Docker Compose
1. ¿Qué es Docker Compose? #
Hasta ahora hemos creado contenedores utilizando comandos como:
1docker run
Por ejemplo:
1docker run -dp 8080:80 nginx
o:
1docker run -d -p 3306:3306 --name mysql -e MYSQL_ROOT_PASSWORD=Ad1234 mysql
Esto funciona bien cuando tenemos pocos contenedores.
Pero imaginemos una aplicación formada por:
1Servidor web 2Base de datos 3Redis 4Backend 5Frontend
Tendríamos que recordar muchos comandos docker run.
Docker Compose permite definir y ejecutar una aplicación formada por varios contenedores utilizando un único archivo YAML. Docker lo plantea precisamente como una forma de gestionar servicios, redes y volúmenes de una aplicación desde una sola configuración.
2. Ejemplo sin Docker Compose #
Supongamos que queremos utilizar:
1Nginx 2+ 3MySQL
Podríamos ejecutar:
1docker run -dp 8080:80 --name web nginx
Y después:
1docker run -d -p 3306:3306 --name mysql -e MYSQL_ROOT_PASSWORD=Ad1234 mysql
Tenemos que recordar:
- Imágenes.
- Nombres.
- Puertos.
- Variables de entorno.
- Volúmenes.
- Redes.
- Opciones de cada contenedor.
Con Docker Compose podemos guardar toda esta configuración en un archivo.
3. El archivo compose.yaml #
Actualmente podemos utilizar un archivo llamado:
1compose.yaml
Dentro describiremos los diferentes servicios de nuestra aplicación. Docker Compose también reconoce nombres compatibles como docker-compose.yaml, aunque compose.yaml es una opción habitual en la documentación actual.
Por ejemplo:
1services: 2 web: 3 image: nginx 4 ports: 5 - "8080:80"
4. Primer ejemplo #
Creamos una carpeta:
1mi-compose
Dentro creamos:
1compose.yaml
Con:
1services: 2 web: 3 image: nginx 4 ports: 5 - "8080:80"
La estructura será:
1mi-compose/ 2└── compose.yaml
5. Arrancar los servicios #
Nos situamos dentro de la carpeta:
1mi-compose
Y ejecutamos:
1docker compose up
Docker leerá:
1compose.yaml
y creará los recursos definidos.
docker compose up crea y arranca los contenedores definidos en la configuración.
6. Ejecutar en segundo plano #
Si queremos recuperar inmediatamente la terminal utilizamos:
1docker compose up -d
La opción:
1-d
significa:
1detached
Igual que en:
1docker run -d
Con docker compose up -d, los servicios quedan ejecutándose en segundo plano.
7. Probar nuestro servidor #
Después:
1docker compose up -d
abrimos:
1http://localhost:8080
Deberíamos ver la página de Nginx.
8. Ver los contenedores de Compose #
Podemos ejecutar:
1docker compose ps
Esto muestra los contenedores correspondientes al proyecto.
También siguen funcionando los comandos generales:
1docker ps
9. Detener y eliminar el proyecto #
Uno de los comandos más útiles es:
1docker compose down
Este comando detiene y elimina los contenedores y redes creados para esa aplicación Compose.
Por ejemplo:
1docker compose down
Después:
1docker compose ps
ya no mostrará los servicios funcionando.
10. up y down #
Podemos recordar:
1docker compose up 2 ↓ 3crear y arrancar 4 5 6docker compose down 7 ↓ 8detener y eliminar
11. services #
La parte principal del archivo es:
1services:
Cada elemento dentro de services representa un servicio de nuestra aplicación.
Por ejemplo:
1services: 2 web: 3 image: nginx 4 5 base-datos: 6 image: mysql
Tenemos dos servicios:
1web 2base-datos
12. image #
La propiedad:
1image:
indica qué imagen queremos utilizar.
Ejemplo:
1services: 2 web: 3 image: nginx
Equivale conceptualmente a utilizar:
1docker run nginx
13. ports #
Podemos publicar puertos mediante:
1ports:
Por ejemplo:
1services: 2 web: 3 image: nginx 4 ports: 5 - "8080:80"
Es equivalente al concepto:
1docker run -p 8080:80 nginx
Tenemos:
1HOST CONTENEDOR 28080 → 80
14. Varios puertos #
Podemos declarar varios:
1services: 2 aplicacion: 3 image: mi-imagen 4 ports: 5 - "8080:80" 6 - "443:443"
Cada puerto se escribe normalmente como una entrada de la lista.
15. environment #
Podemos definir variables de entorno con:
1environment:
Por ejemplo:
1services: 2 mysql: 3 image: mysql 4 environment: 5 MYSQL_ROOT_PASSWORD: Ad1234
La propiedad environment permite establecer variables dentro del contenedor.
16. Ejemplo de MySQL con Compose #
En lugar de:
1docker run -d -p 3306:3306 --name mysql -e MYSQL_ROOT_PASSWORD=Ad1234 mysql
podemos escribir:
1services: 2 mysql: 3 image: mysql 4 ports: 5 - "3306:3306" 6 environment: 7 MYSQL_ROOT_PASSWORD: Ad1234
Y ejecutar:
1docker compose up -d
17. Ventaja #
Ahora ya no necesitamos recordar:
1docker run -d -p 3306:3306 --name mysql -e MYSQL_ROOT_PASSWORD=Ad1234 mysql
Toda la configuración queda almacenada en:
1compose.yaml
Esto permite compartirla con otros compañeros.
18. Volúmenes con Docker Compose #
También podemos montar una carpeta.
Por ejemplo:
1services: 2 web: 3 image: nginx 4 ports: 5 - "8080:80" 6 volumes: 7 - ./web:/usr/share/nginx/html
Tenemos:
1./web 2 ↓ 3/usr/share/nginx/html
19. Ejemplo práctico Nginx #
Creamos:
1proyecto/ 2│ 3├── compose.yaml 4└── web/ 5 └── index.html
En:
1web/index.html
escribimos:
1<h1>Docker Compose</h1> 2<p>Servidor creado con Compose</p>
20. compose.yaml #
Creamos:
1services: 2 web: 3 image: nginx 4 ports: 5 - "8080:80" 6 volumes: 7 - ./web:/usr/share/nginx/html
Arrancamos:
1docker compose up -d
Abrimos:
1http://localhost:8080
21. Volúmenes con nombre #
También podemos definir volúmenes gestionados por Docker.
Por ejemplo:
1services: 2 mysql: 3 image: mysql 4 environment: 5 MYSQL_ROOT_PASSWORD: Ad1234 6 volumes: 7 - datos-mysql:/var/lib/mysql 8 9volumes: 10 datos-mysql:
Tenemos:
1datos-mysql 2 ↓ 3/var/lib/mysql
22. Persistencia con MySQL #
El volumen:
1datos-mysql
permite mantener la información aunque se vuelva a crear el contenedor.
Docker Compose puede gestionar conjuntamente servicios y volúmenes desde el archivo YAML.
23. Ejemplo completo MySQL #
1services: 2 mysql: 3 image: mysql 4 ports: 5 - "3306:3306" 6 environment: 7 MYSQL_ROOT_PASSWORD: Ad1234 8 volumes: 9 - datos-mysql:/var/lib/mysql 10 11volumes: 12 datos-mysql:
Ejecutamos:
1docker compose up -d
24. Acceder a MySQL #
Podemos seguir utilizando:
1docker exec
Primero localizamos el contenedor:
1docker ps
Pero Docker Compose también dispone de:
1docker compose exec
Por ejemplo:
1docker compose exec mysql mysql -uroot -p
En este caso:
1mysql
es el nombre del servicio declarado en:
1services: 2 mysql:
25. docker compose exec #
La sintaxis general es:
1docker compose exec <servicio> <comando>
Por ejemplo:
1docker compose exec web bash
Permite ejecutar una shell en el servicio web.
26. Dos servicios #
Vamos a crear ahora:
1Nginx 2+ 3MySQL
Nuestro archivo podría ser:
1services: 2 3 web: 4 image: nginx 5 ports: 6 - "8080:80" 7 8 mysql: 9 image: mysql 10 ports: 11 - "3306:3306" 12 environment: 13 MYSQL_ROOT_PASSWORD: Ad1234
Con un único:
1docker compose up -d
arrancamos ambos servicios.
27. Comprobar los servicios #
Ejecutamos:
1docker compose ps
Podremos ver:
1web 2mysql
funcionando.
28. Ver logs #
Podemos consultar los logs de todos los servicios:
1docker compose logs
También los de un servicio concreto:
1docker compose logs mysql
29. Seguir los logs #
Podemos utilizar:
1docker compose logs -f
La opción:
1-f
permite seguir la salida de los logs.
Esto es muy útil para servidores y aplicaciones.
30. Detener servicios sin eliminarlos #
Podemos ejecutar:
1docker compose stop
Los contenedores quedan detenidos.
Después podemos volver a arrancarlos:
1docker compose start
31. Diferencia entre stop y down #
docker compose stop #
1docker compose stop
Detiene los contenedores pero los mantiene.
Después:
1docker compose start
puede volver a arrancarlos.
docker compose down #
1docker compose down
detiene y elimina los contenedores del proyecto y sus redes asociadas.
Podemos recordar:
1stop 2 ↓ 3parar 4 5 6down 7 ↓ 8desmontar el proyecto
32. Reiniciar servicios #
Podemos utilizar:
1docker compose restart
También podemos reiniciar únicamente uno:
1docker compose restart web
33. Construir nuestra propia imagen #
Docker Compose no tiene por qué utilizar solamente imágenes de Docker Hub.
Podemos indicarle que construya una imagen desde un Dockerfile.
Utilizamos:
1build:
Por ejemplo:
1services: 2 web: 3 build: . 4 ports: 5 - "8080:80"
Esto indica que debe utilizar el Dockerfile del directorio actual.
Compose permite que un servicio use build para construir su imagen a partir del proyecto.
34. Ejemplo con Dockerfile #
Tenemos:
1proyecto/ 2│ 3├── Dockerfile 4├── compose.yaml 5└── index.html
Dockerfile:
1FROM nginx 2 3COPY index.html /usr/share/nginx/html/index.html
35. compose.yaml #
1services: 2 web: 3 build: . 4 ports: 5 - "8080:80"
Ejecutamos:
1docker compose up -d --build
Compose construirá la imagen y arrancará el servicio.
36. --build #
La opción:
1--build
indica que queremos reconstruir las imágenes necesarias antes de arrancar.
Por ejemplo:
1docker compose up -d --build
Es especialmente útil después de modificar:
1Dockerfile
o archivos que se incorporan a nuestra imagen.
37. docker compose build #
También podemos construir explícitamente:
1docker compose build
Después:
1docker compose up -d
38. depends_on #
Cuando tenemos varios servicios, uno puede depender de otro.
Por ejemplo:
1APLICACIÓN 2 ↓ 3MYSQL
Podemos indicar esta relación mediante:
1depends_on:
Ejemplo:
1services: 2 3 app: 4 image: mi-app 5 depends_on: 6 - mysql 7 8 mysql: 9 image: mysql
Esto describe la dependencia entre servicios.
Para casos donde no basta con que el contenedor exista y queremos esperar a que el servicio esté realmente saludable, Compose también admite condiciones ligadas a healthcheck.
39. Redes automáticas #
Una característica muy importante de Docker Compose es que los servicios pueden comunicarse entre sí utilizando el nombre del servicio.
Supongamos:
1services: 2 3 app: 4 image: mi-app 5 6 mysql: 7 image: mysql
Desde app podremos referirnos al servidor de base de datos mediante:
1mysql
en lugar de tener que conocer su dirección IP.
Docker Compose crea una red por defecto para los servicios del proyecto, permitiendo que puedan encontrarse por nombre.
40. Ejemplo conceptual #
Tenemos:
1CONTENEDOR APP 2 │ 3 │ servidor = mysql 4 ↓ 5CONTENEDOR MYSQL
La aplicación podría utilizar:
1Host: mysql 2Puerto: 3306
No necesitaríamos buscar la IP interna con:
1docker inspect
41. No siempre hay que publicar el puerto #
Supongamos que MySQL solo será utilizado por nuestra aplicación.
Podemos tener:
1services: 2 3 app: 4 image: mi-app 5 ports: 6 - "8080:80" 7 8 mysql: 9 image: mysql 10 environment: 11 MYSQL_ROOT_PASSWORD: Ad1234
No sería obligatorio publicar:
13306:3306
si únicamente queremos que otros contenedores de la aplicación accedan a MySQL.
La propiedad expose permite documentar/exponer puertos para comunicación entre servicios sin publicarlos en el host.
42. Variables mediante archivo .env #
Podemos evitar escribir determinados valores directamente dentro de:
1compose.yaml
Utilizando:
1.env
Por ejemplo:
1MYSQL_PASSWORD=Ad1234 2PUERTO_WEB=8080
43. Utilizar las variables #
Podemos escribir:
1services: 2 3 web: 4 image: nginx 5 ports: 6 - "${PUERTO_WEB}:80" 7 8 mysql: 9 image: mysql 10 environment: 11 MYSQL_ROOT_PASSWORD: ${MYSQL_PASSWORD}
Compose admite interpolación y configuración mediante variables de entorno y archivos .env.
44. Importante sobre contraseñas #
Aunque este sistema es cómodo para prácticas, no debemos considerar una contraseña escrita directamente en compose.yaml como una solución segura para producción.
Por ejemplo:
1MYSQL_ROOT_PASSWORD: Ad1234
es sencillo para clase.
Para entornos reales hay mecanismos específicos para gestionar secretos; la propia documentación de Docker recomienda no utilizar variables de entorno para introducir información sensible cuando se dispone de mecanismos de secretos.
45. Ejemplo completo #
Vamos a crear:
1Nginx 2+ 3MySQL 4+ 5volumen de MySQL
Archivo:
1services: 2 3 web: 4 image: nginx 5 ports: 6 - "8080:80" 7 volumes: 8 - ./web:/usr/share/nginx/html 9 10 mysql: 11 image: mysql 12 environment: 13 MYSQL_ROOT_PASSWORD: Ad1234 14 MYSQL_DATABASE: cebem 15 volumes: 16 - datos-mysql:/var/lib/mysql 17 18volumes: 19 datos-mysql:
46. Estructura del proyecto #
1proyecto/ 2│ 3├── compose.yaml 4│ 5└── web/ 6 └── index.html
47. Arrancar #
Ejecutamos:
1docker compose up -d
Comprobamos:
1docker compose ps
48. Acceder a la web #
Abrimos:
1http://localhost:8080
49. Acceder a MySQL #
Ejecutamos:
1docker compose exec mysql mysql -uroot -p
Introducimos:
1Ad1234
Después:
1SHOW DATABASES;
Deberíamos encontrar:
1cebem
50. Detener la aplicación #
Podemos utilizar:
1docker compose stop
Después:
1docker compose start
51. Eliminar los contenedores #
Utilizamos:
1docker compose down
El volumen declarado por separado puede mantenerse, permitiendo conservar los datos.
52. Eliminar también los volúmenes #
Si realmente queremos eliminar también los volúmenes asociados al proyecto podemos utilizar:
1docker compose down -v
Debemos tener cuidado porque podemos estar eliminando datos persistentes.
53. Práctica guiada #
Crea:
1practica-compose/ 2│ 3├── compose.yaml 4└── web/ 5 └── index.html
Dentro de index.html:
1<h1>Práctica Docker Compose</h1> 2<p>1º DAM</p>
54. compose.yaml de la práctica #
1services: 2 3 web: 4 image: nginx 5 ports: 6 - "8085:80" 7 volumes: 8 - ./web:/usr/share/nginx/html
Ejecuta:
1docker compose up -d
Comprueba:
1docker compose ps
Abre:
1http://localhost:8085
Después:
1docker compose down
55. Práctica con MySQL #
Crea:
1services: 2 3 mysql: 4 image: mysql 5 ports: 6 - "3306:3306" 7 environment: 8 MYSQL_ROOT_PASSWORD: Ad1234 9 MYSQL_DATABASE: curso 10 volumes: 11 - mysql-datos:/var/lib/mysql 12 13volumes: 14 mysql-datos:
Arranca:
1docker compose up -d
56. Acceder #
Ejecuta:
1docker compose exec mysql mysql -uroot -p
Después:
1SHOW DATABASES;
Comprueba que existe:
1curso
57. Probar la persistencia #
Dentro de MySQL:
1USE curso;
Crea:
1CREATE TABLE alumno ( 2 id INT PRIMARY KEY, 3 nombre VARCHAR(50) 4);
Inserta:
1INSERT INTO alumno VALUES (1, 'Ana');
Sal:
1exit;
58. Eliminar y recrear los contenedores #
Ejecuta:
1docker compose down
Después:
1docker compose up -d
Vuelve a conectarte:
1docker compose exec mysql mysql -uroot -p
Y comprueba:
1USE curso; 2SELECT * FROM alumno;
Los datos deberían seguir existiendo gracias al volumen.