Implantación de aplicaciones web PHP en docker
Introducción
En este post vamos a reaizar la implantación de una aplicación web PHP en docker. Vamos a hacer uso de Bookmedik. Crearemos una imagen Docker para implementar dicha aplicación.
Aspectos a tener en cuenta
-
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. Deberemos crear un usuario con su contraseña en la base de datos. La base de datos se llama bookmedik y se crea al ejecutar el script.
- Ejecuta el contenedor mariadb y carga los datos del script schema.sql. Para más información.
- 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 deberá 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:
- Que modifique el fichero
core\controller\Database.phppara que lea las variables de entorno. Para obtener las variables de entorno en PHP usar la función getenv. Para más información. - Que se inicialice la base de datos con el fichero
schema.sql. - Que ejecute el servidor web.
- Que modifique el fichero
- El contenedor que crearemos debe tener un volumen para guardar los logs del servidor web.
- La imagen la tenemos que crear en tu entorno de desarrollo con el comando docker build.
Preparamos el escenario
Lo primero que deberemos hacer es cerciorarnos de que tenemos instalado docker en nuestro entorno de desarrollo. Para ello, ejecutamos el siguiente comando:
$ docker --version
En caso de no tenerlo, lo instalaremos con el siguiente comando:
sudo apt install docker.io docker compose
Clonamos el repositorio de la aplicación web:
git clone https://github.com/evilnapsis/bookmedik.git
Tarea 1: Creación de una imagen docker con una aplicación web desde una imagen base
Llegados a este punto, y teniendo el repositorio forkado a nuestro usuario, vamos a modificar el fichero schema.sql para que se ejecute al crear el contenedor. Por ello, deberemos eliminar las siguientes líneas:
CREATE DATABASE bookmedik;
USE bookmedik;
Nos dirigimos al fichero core/controller/Database.php para que lea las variables de entorno. Para ello, debemos modificar las siguientes líneas:
<?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;
}
}
?>
Ya teniendo todo esto realizado, vamos a crear el fichero Dockerfile para crear la imagen docker. Para ello, debemos crear un fichero llamado Dockerfile en la raíz del proyecto y añadir el siguiente contenido:
FROM debian:bullseye
MAINTAINER María Jesús Alloza Rodríguez "mariajesus.allozarodriguez@gmail.com"
RUN apt-get update && apt-get upgrade -y && apt-get install apache2 libapache2-mod-php php php-mysql mariadb-client -y && apt-get clean && rm -rf /var/lib/apt/lists/*
COPY bookmedik /var/www/html/
ADD script.sh /opt/
RUN chmod +x /opt/script.sh && rm /var/www/html/index.html
ENTRYPOINT ["/opt/script.sh"]
Y el fichero 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
El script que hemos creado, tendrá como destino el directorio raíz del repositorio, para añadirlo al contenedor. Y ya podríamos crear la imagen del contenedor con el siguiente comando:
docker build -t legnakra/bookmedik:v1 .
Si ejecutamos docker images, podremos ver que la imagen se ha creado correctamente:
Tarea 2: Despliegue en el entorno de desarrollo
En esta tarea, crearemos un scritp con docker-compose que levantará el escenario con los contenedores. Debemos tener en cuenta que para acceder a la aplicación, las credenciales serán: Usuario: admin // contraseña: admin.
Por ello, creamos el fichero docker-compose.yaml para levantar ambos contenedores:
version: '3.8'
services:
bookmedik:
container_name: bookmedik-app
image: legnakra/bookmedik:v1
restart: always
environment:
USUARIO_BOOKMEDIK: admin
CONTRA_BOOKMEDIK: admin
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: admin
MARIADB_PASSWORD: admin
volumes:
- mariadb_data:/var/lib/mysql
volumes:
mariadb_data:
Levantamos el escenario con docker compose up -dy con docker ps podemos ver que los contenedores se han levantado correctamente:
Si accedemos a la aplicación, podremos ver que funciona correctamente:
Tarea 3: Creación de una imagen docker con una aplicación web desde una imagen PHP
Ahora le toca el turno a la imagen de PHP. Para ello, crearemos un fichero llamado Dockerfile en la raíz del proyecto y añadiremos el siguiente contenido:
FROM php:7.4-apache-bullseye
MAINTAINER María Jesús Alloza Rodríguez "mariajesus.allozarodriguez@gmail.com"
RUN apt-get update && apt-get upgrade -y && docker-php-ext-install mysqli pdo pdo_mysql && apt-get install mariadb-client -y && apt-get 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"]
Y creamos una nueva imagen:
docker build -t legnakra/bookmedik:v2 .
EL fichero docker-compose.yaml quedaría de la siguiente forma:
version: '3.8'
services:
bookmedik:
container_name: bookmedik-app
image: legnakra/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:latest
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:
Realizamos el despliegue con docker-compose up -d y como vemos en la imagen anterior, ambos contenedores se están ejecutando y en la siguiente, comprobamos que la imagen se ha creado correctamente:
Si accedemos a la aplicación, podremos ver que funciona correctamente:
Tarea 4: Ejecución de una aplicación PHP en docker con nginx
En esta tarea, vamos a tener que crear dos imágenes:
- Una que contrendrá la aplicación PHP y será nginx la encargada de servirla.
- Otra que tendrá los modulos de PHP necesarios para que la aplicación funcione.
Creamos el Dockerfile con php-fpm y sus correspondientes modulos:
FROM php:7.4-fpm
MAINTAINER María Jesús Alloza Rodríguez "mariajesus.allozarodriguez@gmail.com"
RUN docker-php-ext-install mysqli
Creamos la imagen:
docker build -t legnakra/php-fpm-mysql:v1 .
Para crear el dockerfile con la aplicación y nginx como servidor web, añadimos el siguiente contenido:
FROM nginx
MAINTAINER María Jesús Alloza Rodríguez "mariajesus.allozarodriguez@gmail.com"
RUN apt-get update && apt-get upgrade -y && apt-get install mariadb-client -y && apt-get 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"]
En el docker file anterior, hemos establecido el fichero default.conf como el fichero de configuración de nginx. Este fichero contendrá la siguiente información:
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;
}
}
Y también deberemos modificar script.sh para adaptarlo a nginx:
#! /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;"
Creamos la imagen:
docker build -t legnakra/bookmedik:v3 .
Antes de modificar el docker-compose.yaml debemos tener en cuenta que:
- Los contenedores que tienen php-fpm y nginx, deben estar en la misma ruta.
- Las variables de entorno que se pasan al contenedor de nginx, deben también estar en el contenedor de php-fpm.
Por lo que el fichero docker-compose.yaml quedaría de la siguiente forma:
version: '3.8'
services:
bookmedik:
container_name: bookmedik-app
image: legnakra/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: legnakra/php-fpm-mysql:v1
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:
Tras todos los pasos anteriores, podemos cerciorarnos en la imagen anterior, que las imágenes que hemos confeccionado se han creado correctamente.
Ahora, ejecutamos el docker compose.yaml:
docker-compose up -d
Y comprobamos que todo funciona como esperábamos:
Si accedemos a la aplicación, podremos ver que funciona correctamente:
Tarea 5: Puesta en producción de nuestra aplicación
Vamos a poner en producción la aplicación a través de nuestro VPS, por lo que lo primero que deberemo de hacer es crear un registro CNAME en nuestro DNS para que apunte a la IP de nuestro VPS.
⬜️ bookmedik.mariatec.es CNAME mariatec.es
Y le generamos un certificado de Let's Encrypt:
certbot certonly --standalone -d bookmedik.mariatec.es
Instalamos docker y docker-compose en nuestro VPS:
sudo apt update && sudo apt upgrade -y && sudo apt install docker.io docker-compose-plugin -y
Al tener las imágenes creadas subidas a Docker Hub, podemos descargarlas en nuestro VPS:
docker pull legnakra/bookmedik:v2
En mi caso he elegido la versión 2, ya que es la que tiene el fichero docker-compose.yaml adaptado para el VPS.
Creamos el virtualhost en nginx para que actúe de proxy inverso:
nano /etc/nginx/sites-available/bookmedik.mariatec.es
server {
listen 80;
listen [::]:80;
server_name bookmedik.mariatec.es;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
ssl on;
ssl_certificate /etc/letsencrypt/live/bookmedik.mariatec.es/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/bookmedik.mariatec.es/privkey.pem;
index index.html index.php index.htm index.nginx-debian.html;
server_name bookmedik.mariatec.es;
location / {
proxy_pass http://localhost:8083;
include proxy_params;
}
}
Creamos el enlace simbólico y reiniciamos nginx:
ln -s /etc/nginx/sites-available/bookmedik /etc/nginx/sites-enabled/
systemctl restart nginx
En mi caso, he subido los ficheros a github, por lo que he clonado el repositorio y el repositorio de Bookmedik en mi VPS, dejando la estructura de carpetas de la siguiente forma:
├── Bookmedik
│ ├── docker-compose.yaml
│ ├── Dockerfile
│ ├── scripts
│ ├── bookmedik
│ │ ├── PhpWord
│ │ ├── core
│ │ ├── assets
│ │ ├── report
│ │ ├── index.php
│ │ ├── instalation.txt
│ │ ├── schema.sql
│ │ ├── logout.php
│ │ ├── README.md
Realizamos la ejecución del comando docker-compose up -d y comprobamos que todo funciona correctamente:
Y como podemos comprobar en la siguiente imagen, la aplicación funciona correctamente.
NOTA: A la hora de hacer el despliegue, debemos cerciorarnos de cambiar el puerto de escucha, dado que puede caber la posibilidad (o que sea muy probable) de que el puerto 8080 esté ocupado. En mi caso, he puesto el puerto de escucha en el 8083 debido a que ya tengo aplicaciones desplegadas en mi VPS.
Tarea 6: Modificación de la aplicación
Vamos a modificar la aplicación para que nos muestre nuestro nombre en la línea <h4 class="title">Acceder a BookMedik</h4>.
Para ello, vamos a utilizar la imagen de legnakra/bookmedik:v1 que ya tiene el fichero docker-compose.yaml para generar la nueva imagen.
nano `core/app/view/login-view.php`
---
<h4 class="title">María Jesús Alloza Rodríguez</h4>
Creamos la nueva imagen
docker build -t legnakra/bookmedik:v1_2 .
La resubimos a DockerHub:
docker push legnakra/bookmedik:v1_2
Eliminamos los contenedores que están en ejecución, modificamos el fichero docker-compose.yaml para que descargue la nueva imagen y ejecutamos el docker-compose.yaml:
docker compose down
nano docker-compose.yaml
version: '3.8'
services:
bookmedik:
container_name: bookmedik-app
image: legnakra/bookmedik:v1_2
restart: always
environment:
USUARIO_BOOKMEDIK: admin
CONTRA_BOOKMEDIK: admin
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: admin
MARIADB_PASSWORD: admin
volumes:
- mariadb_data:/var/lib/mysql
volumes:
mariadb_data:
Y con esto daríamos terminado como desplegar una aplicación PHP en un VPS con contenedores Docker.
Conclusiones
Docker es una herramienta muy útil para el despliegue de aplicaciones, ya que nos permite crear contenedores que contienen todo lo necesario para que la aplicación funcione correctamente. Además, nos permite crear imágenes que podemos subir a Docker Hub para que otras personas puedan descargarlas y utilizarlas.
Bibliografía
https://www.digitalocean.com/community/tutorials/how-to-install-and-use-docker-on-ubuntu-20-04-es