Práctica: Implantación de aplicaciones web PHP en Docker
Imaginemos que el equipo de desarrollo de nuestra empresa ha desarrollado una aplicación PHP que se llama BookMedik.
Queremos crear una imagen Docker para implantar dicha aplicación.
Tenemos que tener en cuenta los siguientes aspectos:
-
Contenedor mariadb
- Es necesario que nuestra aplicación guarde su información en un contenedor docker mariadb.
- El script para generar la base de datos y los registros lo encuentras en el repositorio y se llama
schema.sql
. Debes crear un usuario con su contraseña en la base de datos. La base de datos se llamabookmedik
. - El contenedor mariadb debe tener un volumen para guardar la base de datos.
-
Contenedor bookmedik
- Vamos a crear tres versiones de la imagen que nos permite implantar la aplicación PHP.
- La imagen debe crear las variables de entorno necesarias con datos de conexión por defecto.
- Al crear un contenedor a partir de estas imágenes se ejecutará un script bash que realizará las siguientes tareas:
- Modifique el fichero
core\controller\Database.php
para que lea las variables de entorno. Para obtener las variables de entorno en PHP usar la función getenv. Para más información. - Inicialice la base de datos con el fichero
schema.sql
. - Ejecute el servidor web.
- Modifique el fichero
- El contenedor que creas debe tener un volumen para guardar los logs del servidor web.
- La imagen la tienes que crear en tu entorno de desarrollo con el comando
docker build
.
Creación del contenedor MariaDB
Primero creamos un contenedor con la imagen mariadb
y que tenga las variables de entorno que nos han indicado. También crearemos una nueva red para la aplicación web.
docker network create red_bookmedik
docker run -d --name bd_mariadb -v bookmedik_vol:/var/lib/mysql --network red_bookmedik -e MARIADB_ROOT_PASSWORD=root -e MARIADB_DATABASE=bookmedik -e MARIADB_USER=bookmedik -e MARIADB_PASSWORD=bookmedik mariadb
Esto será para realizar las pruebas necesarias y comprobar que la imagen de la aplicación funcione como es debido.
Tarea 1: Creación de una imagen docker con una aplicación web desde una imagen base
- Vamos a crear una imagen que se llame usuario/bookmedik:v1.
- Crea una imagen docker con la aplicación desde una imagen base de debian o ubuntu.
En primer lugar hemos hecho un fork del repositorio de bookmedik y lo hemos clonado en nuestro entorno de desarrollo. Una vez hecho esto, vamos a modificar el fichero schema.sql
para que podamos ejecutarlo en un contenedor que ya tendrá una base de datos creada. Así pues, eliminamos las siguientes líneas de ese fichero:
create database bookmedik;
use bookmedik;
A continuación modificamos el fichero core/controller/Database.php
para que se configure a través de las variables de entorno que introduzcamos al crear el contenedor:
<?php
class Database {
public static $db;
public static $con;
function Database(){
$this->user=getenv('USUARIO_BOOKMEDIK');$this->pass=getenv('CONTRA_BOOKMEDIK');$this->host=getenv('DATABASE_HOST');$this->ddbb=getenv('NOMBRE_DB');
}
function connect(){
$con = new mysqli($this->host,$this->user,$this->pass,$this->ddbb);
$con->query("set sql_mode=''");
return $con;
}
public static function getCon(){
if(self::$con==null && self::$db==null){
self::$db = new Database();
self::$con = self::$db->connect();
}
return self::$con;
}
}
?>
Con esto ya podemos crear el Dockerfile que usaremos para crear la imagen:
nano Dockerfile
FROM debian:bullseye
MAINTAINER Daniel Parrales García "daniparrales16@gmail.com"
RUN apt update && apt upgrade -y && apt install apache2 libapache2-mod-php php php-mysql mariadb-client -y && apt clean && rm -rf /var/lib/apt/lists/*
ADD bookmedik /var/www/html/
ADD script.sh /opt/
RUN chmod +x /opt/script.sh && rm /var/www/html/index.html
ENTRYPOINT ["/opt/script.sh"]
Como vemos, al final le indicamos que ejecute un script. Dicho script lo hemos creado nosotros, y en él hacemos que introduzca la información del fichero schema.sql
en la base de datos y hacemos que ejecute apache en modo demonio. El contenido del script es el siguiente:
nano script.sh
#! /bin/sh
mysql -u $USUARIO_BOOKMEDIK --password=$CONTRA_BOOKMEDIK -h $DATABASE_HOST $NOMBRE_DB < /var/www/html/schema.sql
/usr/sbin/apache2ctl -D FOREGROUND
Este script lo hemos introducido en el directorio bookmedik
(el que hemos obtenido de la clonación de github) para que se añada al contenedor junto con el contenido de dicho directorio. Con esto ya podemos crear la imagen con el siguiente comando:
docker build -t dparrales/bookmedik:v1 .
Una vez creada, ya la podemos ver en nuestro registro local:
Los ficheros que han sido creados se encuentran en mi repositorio de Github.
Tarea 2: Despliegue en el entorno de desarrollo
- Crea un script con docker-compose que levante el escenario con los dos contenedores.
- Recuerda que para acceder a la aplicación: Usuario: admin, contraseña: admin.
Así pues, crearemos un fichero docker-compose.yaml
con la configuración necesaria para levantar los dos contenedores:
nano docker-compose.yaml
version: '3.1'
services:
bookmedik:
container_name: bookmedik-app
image: dparrales/bookmedik:v1
restart: always
environment:
USUARIO_BOOKMEDIK: bookmedik
CONTRA_BOOKMEDIK: bookmedik
DATABASE_HOST: bd_mariadb
NOMBRE_DB: bookmedik
ports:
- 8081:80
depends_on:
- db
db:
container_name: bd_mariadb
image: mariadb
restart: always
environment:
MARIADB_ROOT_PASSWORD: root
MARIADB_DATABASE: bookmedik
MARIADB_USER: bookmedik
MARIADB_PASSWORD: bookmedik
volumes:
- mariadb_data:/var/lib/mysql
volumes:
mariadb_data:
Ahora levantamos los contenedores:
docker-compose up -d
Podemos verlos funcionando con el siguiente comando:
Y si entramos desde el navegador web, debería funcionar correctamente:
Tarea 3: Creación de una imagen docker con una aplicación web desde una imagen PHP
- Vamos a crear una imagen que se llame
usuario/bookmedik:v2
. - Realiza la imagen docker de la aplicación a partir de la imagen oficial PHP que encuentras en docker hub. Lee la documentación de la imagen para configurar una imagen con apache2 y php, además seguramente tengas que instalar alguna extensión de php.
- Modifica el fichero
docker-compose.yml
para probar esta imagen.
Primero creamos el Dockerfile:
nano Dockerfile
FROM php:7.4-apache-bullseye
MAINTAINER Daniel Parrales García "daniparrales16@gmail.com"
RUN apt update && apt upgrade -y && docker-php-ext-install mysqli pdo pdo_mysql && apt install mariadb-client -y && apt clean && rm -rf /var/lib/apt/lists/*
ADD bookmedik /var/www/html/
ADD script.sh /opt/
RUN chmod +x /opt/script.sh
ENTRYPOINT ["/opt/script.sh"]
Ahora creamos la nueva imagen:
docker build -t dparrales/bookmedik:v2 .
En el fichero docker-compose.yaml
solo debemos cambiar la versión:
nano docker-compose.yaml
version: '3.1'
services:
bookmedik:
container_name: bookmedik-app
image: dparrales/bookmedik:v2
restart: always
environment:
USUARIO_BOOKMEDIK: bookmedik
CONTRA_BOOKMEDIK: bookmedik
DATABASE_HOST: bd_mariadb
NOMBRE_DB: bookmedik
ports:
- 8081:80
depends_on:
- db
db:
container_name: bd_mariadb
image: mariadb
restart: always
environment:
MARIADB_ROOT_PASSWORD: root
MARIADB_DATABASE: bookmedik
MARIADB_USER: bookmedik
MARIADB_PASSWORD: bookmedik
volumes:
- mariadb_data:/var/lib/mysql
volumes:
mariadb_data:
Ahora levantamos los contenedores:
docker-compose up -d
Vemos como se están ejecutando los contenedores:
Y vemos también la imagen que se ha creado:
Por último, accedemos a la aplicación web desde el navegador:
Tarea 4: Ejecución de una aplicación PHP en docker con nginx
- Vamos a crear una imagen que se llame usuario/bookmedik:v3.
- En este caso queremos usar un contenedor que utilice nginx para servir la aplicación PHP. Puedes crear la imagen desde una imagen base debian o ubuntu o desde la imagen oficial de nginx.
- Vamos a crear otro contenedor que sirva php-fpm.
- Para que funcione de forma adecuada el php-fpm tiene que tener acceso al directorio donde se encuentra la aplicación.
- Y finalmente nuestro contenedor con la aplicación.
- Crea un script con docker compose que levante el escenario con los tres contenedores.
Para empezar, tenemos que crear dos imágenes: una que va a tener la aplicación y la va a servir a través de nginx, y otra que a tener php-fpm con los módulos necesarios instalados. Así pues, creamos los dos Dockerfile:
- El Dockerfile con php-fpm y los módulos:
FROM php:7.3-fpm-bullseye
MAINTAINER Daniel Parrales García "daniparrales16@gmail.com"
RUN docker-php-ext-install mysqli pdo pdo_mysql
Y construimos la imagen:
docker build -t dparrales/php-fpm-mysql:v1 .
- El Dockerfile con la aplicación y nginx:
FROM nginx
MAINTAINER Daniel Parrales García "daniparrales16@gmail.com"
RUN apt update && apt upgrade -y && apt install mariadb-client -y && apt clean && rm -rf /var/lib/apt/lists/*
ADD default.conf /etc/nginx/conf.d/
ADD bookmedik /usr/share/nginx/html
ADD script.sh /opt/
RUN chmod +x /opt/script.sh && rm /usr/share/nginx/html/index.html
ENTRYPOINT ["/opt/script.sh"]
Y construimos la imagen:
docker build -t dparrales/bookmedik:v3 .
Como vemos, en el anterior fichero hacemos referencia al fichero default.conf
. Este fichero lo hemos añadido y sustituirá al que hay por defecto, añadiendo la configuración de php-fpm al virtualhost:
nano default.conf
server {
listen 80;
listen [::]:80;
server_name localhost;
error_log /var/log/nginx/error.log;
access_log /var/log/nginx/access.log;
root /usr/share/nginx/html;
index index.php index.html;
location ~ \.php$ {
try_files $uri =404;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass book_php:9000;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
}
}
El fichero script.sh
ha sido modificado para adaptarse a nginx:
nano script.sh
#! /bin/sh
sleep 10
mysql -u $USUARIO_BOOKMEDIK --password=$CONTRA_BOOKMEDIK -h $DATABASE_HOST $NOMBRE_DB < /usr/share/nginx/html/schema.sql
nginx -g "daemon off;"
Una vez explicado esto, podemos pasar a crear el fichero docker-compose.yaml
:
nano docker-compose.yaml
version: '3.1'
services:
bookmedik:
container_name: bookmedik-app
image: dparrales/bookmedik:v3
restart: always
environment:
USUARIO_BOOKMEDIK: bookmedik
CONTRA_BOOKMEDIK: bookmedik
DATABASE_HOST: bd_mariadb
NOMBRE_DB: bookmedik
ports:
- 8082:80
depends_on:
- db
- php
volumes:
- phpdocs:/usr/share/nginx/html/
db:
container_name: bd_mariadb
image: mariadb
restart: always
environment:
MARIADB_ROOT_PASSWORD: root
MARIADB_DATABASE: bookmedik
MARIADB_USER: bookmedik
MARIADB_PASSWORD: bookmedik
volumes:
- mariadb_data:/var/lib/mysql
php:
container_name: book_php
image: php:7.3-fpm-bullseye
restart: always
environment:
USUARIO_BOOKMEDIK: bookmedik
CONTRA_BOOKMEDIK: bookmedik
DATABASE_HOST: bd_mariadb
NOMBRE_DB: bookmedik
volumes:
- phpdocs:/usr/share/nginx/html/
volumes:
mariadb_data:
phpdocs:
En el fichero anterior hay que destacar dos cosas:
- El volumen que comparten el contenedor nginx y el contenedor php-fpm deben tener la misma ruta. No importa que dicha ruta no exista en el contenedor php-fpm.
- Las variables de entorno deben estar también en el contenedor php-fpm.
Dicho todo lo anterior, podemos comprobar que las imágenes se han creado correctamente:
Ahora creamos los contenedores con el siguiente comando:
docker-compose up -d
Vemos que están funcionando:
Ahora ya podemos acceder a través del navegador web:
Con esto hemos terminado esta tarea.
Tarea 5: Puesta en producción de nuestra aplicación
- Elige una de las tres imágenes y súbela a Docker Hub.
- En tu VPS instala Docker y utilizando el docker-compose.yml correspondiente, crea un contenedor en ella de la aplicación.
- Configura el nginx de tu VPS para que haga de proxy inverso y nos permita acceder a la aplicación con
https://bookmedik.tudominio.xxx
.
En primero lugar, tenemos que crear el registro CNAME correspondiente en nuestra zona DNS:
A continuación, en la VPS, debemos obtener el certificado de “Let’s Encrypt” para ese registro:
certbot certonly --standalone -d bookmedik.sysadblog.com
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Plugins selected: Authenticator standalone, Installer None
Requesting a certificate for bookmedik.sysadblog.com
Performing the following challenges:
http-01 challenge for bookmedik.sysadblog.com
Cleaning up challenges
Problem binding to port 80: Could not bind to IPv4 or IPv6.
root@blackstar:~# systemctl stop nginx
root@blackstar:~# certbot certonly --standalone -d bookmedik.sysadblog.com
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Plugins selected: Authenticator standalone, Installer None
Requesting a certificate for bookmedik.sysadblog.com
Performing the following challenges:
http-01 challenge for bookmedik.sysadblog.com
Waiting for verification...
Cleaning up challenges
IMPORTANT NOTES:
- Congratulations! Your certificate and chain have been saved at:
/etc/letsencrypt/live/bookmedik.sysadblog.com/fullchain.pem
Your key file has been saved at:
/etc/letsencrypt/live/bookmedik.sysadblog.com/privkey.pem
Your certificate will expire on 2022-05-03. To obtain a new or
tweaked version of this certificate in the future, simply run
certbot again. To non-interactively renew *all* of your
certificates, run "certbot renew"
- If you like Certbot, please consider supporting our work by:
Donating to ISRG / Let's Encrypt: https://letsencrypt.org/donate
Donating to EFF: https://eff.org/donate-le
Ahora debemos instalar docker
y docker-compose
si no lo está ya:
apt install docker.io docker-compose
Podemos comprobar que las imágenes de encuentran subidas a mi cuenta en “Docker Hub”:
Ahora hay que elegir una de las imágenes anteriores para desplegarla en la VPS. Tras pensarlo, me he decidido por la versión dos. Así pues, nos llevamos el fichero docker-compose.yaml
con la versión dos a la VPS y levantamos los contenedores (he modificado el puerto, ya que ese estaba ocupado):
Ya solo queda crear el virtualhost en nginx que actuará como proxy inverso:
nano /etc/nginx/sites-available/bookmedik
server {
listen 80;
listen [::]:80;
server_name bookmedik.sysadblog.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
ssl on;
ssl_certificate /etc/letsencrypt/live/bookmedik.sysadblog.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/bookmedik.sysadblog.com/privkey.pem;
index index.html index.php index.htm index.nginx-debian.html;
server_name bookmedik.sysadblog.com;
location / {
proxy_pass http://localhost:8083;
include proxy_params;
}
}
Ahora creamos el enlace simbólico:
ln -s /etc/nginx/sites-available/bookmedik /etc/nginx/sites-enabled/bookmedik
Y reiniciamos el servicio de nginx:
systemctl restart nginx
Ya podemos acceder desde el navegador web:
Con esto hemos terminado esta tarea.
Tarea 6: Modificación de la aplicación
- En el entorno de desarrollo vamos a hacer una modificación de la aplicación. Por ejemplo modifica el fichero
core/app/view/login-view.php
y pon tu nombre en la línea<h4 class="title">Acceder a BookMedik</h4>
. - Vamos a trabajar con la primera imagen que construimos. Vuelve a crear la imagen con la etiqueta
v1_2
. - Cambia el docker-compose para probar el cambio.
- Modifica la aplicación en producción.
Como nos han indicado, modificaremos el fichero core/app/view/login-view.php
:
<h4 class="title">Acceder a BookMedik Daniel Parrales</h4>
Y creamos la imagen nueva:
docker build -t dparrales/bookmedik:v1_2 .
Esta imagen la subimos también a “Docker Hub”:
docker login
docker push dparrales/bookmedik:v1_2
Podemos verla en dicha página:
Ahora, en la vps tenemos que eliminar los contenedores que hemos creado y volverlos a crear tras actualizar el fichero docker-compose.yaml
a la nueva versión. Una vez hecho esto, podemos acceder a la web y ver si se han producido los cambios:
Como vemos, se han producido los cambios, por lo que podemos decir que la práctica ha sido un éxito.