1. Docker Compose
1.1 ¿Qué es y para qué sirve?
Docker compose es una herramienta para definir y ejecutar aplicaciones multi-contenedor en docker. En él, con un solo fichero compose.yml podemos levantar múltiples contenedores de forma centralizada y sencilla (con un solo comando, mucho más fácil que como hemos hecho hasta ahora).
1.2 Creando contenedores con docker compose
Para crear contenedores con docker compose el fichero debe llamarse de una de las siguientes maneras:
- compose.yaml
- compose.yml
- docker-compose.yaml
- docker-compose.yml
Al ejecutar el comando docker compose up automáticamente el demonio de docker buscará siempre uno de esos ficheros en el directorio actual. Si no lo hay, no funcionará.
Veamos un ejemplo básico de un index.php sencillo:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Página web con HTML y PHP</title>
</head>
<body>
<h1>Mi página web</h1>
<?php
echo "<p>Hola mundo!</p>";
?>
</body>
</html>
Tenemos ese fichero que muestra un "Hello world" usando un echo de PHP. Vamos a ver como, sin necesidad de instalar PHP en nuestro sistema, podemos dockerizar esta aplicación.
Este es un fichero de ejemplo de docker compose que nos levanta un contenedor de PHP con Apache2 (php:8.4-apache). Veamos para qué sirve cada una de las etiquetas que hemos puesto:
- Etiqueta "services": Esta es siempre la primera línea del yaml. En todos los ficheros de docker compose la pondremos de primera.
- Nombre del contenedor (en este caso: mi-php): a partir de aquí definiremos las propiedades del contenedor (mapeo de puertos, imagen en la que se basa, si se usan volúmenes o puntos de montaje, etc.).
- Etiqueta "image": indico la imagen que uso como base del contenedor. Equivalente a: docker container run --name mi-php php:8.4-apache.
- Etiqueta "ports": para mapear puertos. Equivalencia: docker container run --name mi-php -p 8080:80 php:8.4-apache
- Nombre del contenedor (en este caso: mi-php): a partir de aquí definiremos las propiedades del contenedor (mapeo de puertos, imagen en la que se basa, si se usan volúmenes o puntos de montaje, etc.).
Bien, vamos a ver como levantar el contenedor. Si NO USAMOS docker compose podríamos hacer lo siguiente:
# Levantar el contenedor de modo que la salida estandar esté ligada al terminal (no podríamos usar el terminal)
docker container run -p 8080:80 --name mi-php php:8.4-apache
# Otra opción (modo detached, podemos seguir usando el terminal)
docker container run -d -p 8080:80 --name mi-php php:8.4-apache # Podemos poner -d -p o -dp
# Si queremos parar y eliminar el contenedor usamos:
docker container stop mi-php
docker container rm mi-php
# Si está parado y queremos arrancarlo de nuevo
docker container start mi-php
Esto ya lo habíamos visto antes. En este caso, NO SE ESTÁ USANDO PARA NADA EL FICHERO compose.yml. Vamos a ver como hacer lo mismo con docker compose:
# Levantar TODOS los contenedores declarados en el compose.yml
docker compose up
# Levantar TODOS los contenedores en modo dettached (-d)
docker compose up -d
# Parar TODOS los contenedores que haya declarados en el compose.yml
docker compose stop
# Eliminar todos los contenedores
docker compose down
# Eliminar todos los contenedores (y sus volúmenes si están declarados)
docker compose down -v
Es mucho más simple. Esto es, solo con hacer docker compose up -d podemos levantar uno (o muchos) contenedores y con docker compose down eliminarlos todos de un plumazo.
Ejercicio
- Crea el fichero .php anterior y el compose.yml (ve escribiéndolo tu línea a línea para interiorizar el conocimiento, no hagas copy-paste).
- Una vez creado, intenta levantarlo con docker compose en modo detached (-d).
- Prueba si funciona la página web... ¿debería funcionar 🤔?.
Probablemente no te funcione la página o te de un error 403 forbidden. Ahora la pregunta es, ¿por qué? ¿Debería enseñarnos el index que creamos? ¿Si lo llegamos a usar con docker container run funcionaría o falta algo más?
Finalmente, apaga el contenedor (no soluciones el problema de código aún, lo haremos en la siguiente sección).
1.3 Añadiendo puntos de montaje
Ya sabemos cositas, pero aún no podemos subir nuestro código al contenedor!!! Vamos a ello, necesitamos un punto de montaje.
Primero de todo, vamos a recordar como lo hacíamos sin compose:
# Sincronizamos la carpeta de mi proyecto, por ejemplo: /home/marcos/desarrollo/mi-php, con la /var/www/html del contenedor
docker container run --name mi-php -d -p 8080:80 -v /home/marcos/desarrollo/mi-php:/var/www/html:/var/www/html php:8.4-apache
La parte de -v /home/marcos/desarrollo/mi-php:/var/www/html añadida al compose.yml sería así:
services:
mi-php:
image: php:8.4-apache
ports:
- "8080:80"
volumes:
- ./:/var/www/html # En este caso vamos a poner la ruta relativa (./ es el directorio actual)
Con esto todo lo que hay en mi directorio actual se copiará al contenedor a la carpeta /var/www/html.
Ejercicio
- Añade el volumen al contenedor del compose.yml.
- Tenemos que destruir el contenedor y crearlo de nuevo para que todo funcione. Mira los contenedores en ejecución con
docker container ls -ay condocker compose ls -a. - Destruye los contenedores con
docker compose... y algo más!. - Crealos de nuevo y prueba a acceder otra vez desde el navegador web. Debería aparecer la página.
1.4 Accediendo a los contenedores
Podemos acceder a los contenedores con el comando habitual (docker container exec).
Ejercicio
- ¿Cómo se llama el contenedor que se ha creado? ¿Qué patrón sigue el nombre?
- Accede al contenedor usando su nombre y lista los ficheros que hay en /var/www/html. ¿Cuáles son?
Como habrás visto, el contenedor sigue el patrón
Usando docker compose --help verás que también existe el parámetro exec (es decir, puedes hacer lo mismo don docker compose exec que has hecho con docker container exec pero ahora puedes usar el nombre mi-php que has definido en el yaml).
Ejercicio
- Ejecuta
docker compose --helpy mira si hay la opción exec. - Prueba de nuevo a abrir un terminal de bash en el contenedor pero ahora usando
docker compose exec. Recuerda, condocker compose execpuedes usar directamente el nombre que has definido en el yaml. - Lista de nuevo lo que hay en /var/www/html.
- Habrás visto que hay un fichero compose.yml en el directorio (lo cuál no está muy bien). Modifica tu fichero compose.yml para que el código de tu aplicación esté en una carpeta "src". La jerarquía debería quedar tal que así:
- ./compose.yml
- ./src/
- ./src/index.php
Una vez has terminado con esto, deberías poder modificar tu código desde Visual Studio Code y esos cambios se reflejarán inmediatamente en la página web. Confírmalo.
1.5 Añadiendo un contenedor de base de datos
Para añadir un nuevo contenedor, simplemente tenemos que hacer lo siguiente:
services:
mi-php:
image: php:8.4-apache
ports:
- "8080:80"
volumes:
- ./:/var/www/html # En este caso vamos a poner la ruta relativa (./ es el directorio actual)
mi-bd:
# ... cosas
Bien, dentro de bd, de nuevo puedes meter imagen, puertos, volúmenes, etc. Ya has hecho esto antes con docker, así que ahora lo haremos con docker compose. La única información adicional que necesitas (si recuerdas bien como lo has hecho usando el comando docker container run) es una variable de entorno con la contraseña de root.
Para añadir variables de entorno:
services:
mi-php:
image: php:8.4-apache
ports:
- "8080:80"
volumes:
- ./:/var/www/html # En este caso vamos a poner la ruta relativa (./ es el directorio actual)
mi-bd:
...
environment:
- VARIABLE_DE_ENTORNO=VALOR1
- VARIABLE_DE_ENTORNO2=VALOR2
- VARIABLE_DE_ENTORNO3=VALOR3
# ...
Ejercicio
- Añade al compose.yml lo necesario para que arranque también una base de datos de MariaDB. Recuerda que tendrás que destruir los contenedores y volver a crearlos con comandos como:
docker compose up -dydocker compose down(asegúrate de que se mantiene en ejecución después de arrancarla).
1.6 Añadiendo volúmenes
El contenedor de base de datos no ejecuta ningún script de creación de base de datos. Según la documentación de la imagen de MariaDB en Dockerhub, si copiamos cualquier script .sql en la carpeta /docker-entrypoint-initdb.d/ este se ejecutará automáticamente la primera vez que creemos el contenedor.
Igual que hemos añadido un punto de montaje (bind mount) con la etiqueta "volume", se hace igual para volúmenes. Puedes crear un volumen para una base de datos de manera que al arrancar y eliminar el contenedor no se pierdan los datos de la base de datos.
1.7 Más comandos de Docker
# Recrea de nuevo los contenedores en caso de que la imagen cambiase (actualiza la imagen y levanta el contenedor de nuevo si la imagen cambió)
docker compose up --build
# Recrea los contenedores
docker compose up --force-recreate
# Elimina los contenedores y las redes y volúmenes anónimas creadas con estos
docker compose down
# Elimina los contenedores y todos sus volúmenes asociados
docker compose down -v
# Validar sintaxis de tu compose.yml y ver el resultado final tal como lo interpreta docker
docker compose config
# Ver la información de todos los servicios que hemos definido en el compose.yml
docker compose logs
1.8 Ejercicios
1.8.1 Añadido de PHPMyAdmin y volúmenes
Este ejercicio consistirá en modificar lo que acabamos de hacer para que se conecte a una base de datos MariaDB. Si eliminas el contenedor la base de datos debe mantenerse intacta. A continuación se dan algunas indicaciones de cómo hacerlo:
- Pídele a ChatGPT u otra IA que te de un script de base de datos con una tabla y un index.php asociado. Añádelo al index y trata de conectarte a base de datos.
- Asegúrate de que al levantar el contenedor se crea la base de datos automáticamente. Esto lo puedes hacer tal como se indica en estos apuntes de teoría, copiando el fichero .sql en
/docker-entrypoint-initdb.d/. Para realizar esta copia puedes crear otro volumen que comparta esa carpeta con una carpeta tuya donde tengas el script de base de datos. - Añade otro contenedor de nombre mi-phpmyadmin que cree un contenedor con PHPMyAdmin y te permita gestionar la base de datos. Tienes una imagen de PHPMyAdmin con toda su documentación en este enlace.
1.8.2 Monta un CMS con compose
En los ejercicios anteriores has montado un Wordpress con docker. Ahora vamos a simplificarlo usando compose.
Busca en Dockerhub una imagen de Wordpress y crea un compose.yml que monte un Wordpress completo.
1.8.3 Monta un Nextcloud
Usa Docker para crear un entorno Nextcloud. Una vez montado el entorno:
- Configura el perfil de administrador.
- Crea cuatro usuarios y dos grupos. Haz que cada grupo tenga dos usuarios.
- Cambia el tema por defecto.
- Instala un cliente de escritorio para Nextcloud y conéctate al sevidor desde él.
Configura los volúmenes que consideres necesarios en el compose.yml (debe haber alguno al menos que consideres útil).