Curso Docker #6: Docker Compose — Aplicaciones Multi-Contenedor
Bienvenido al Curso de Docker - Parte 6 de 10. Hasta ahora hemos trabajado con contenedores individuales, pero las aplicaciones reales suelen necesitar multiples servicios trabajando juntos: una base de datos, un servidor web, un cache, etc. Gestionar todo esto manualmente con docker run se vuelve tedioso y propenso a errores.

Fuente: Wikimedia Commons
En este articulo aprenderemos Docker Compose, la herramienta oficial de Docker para definir y ejecutar aplicaciones multi-contenedor con un solo archivo de configuracion.
Que es Docker Compose y por que lo necesitas
Docker Compose es una herramienta que permite definir aplicaciones multi-contenedor usando un archivo YAML llamado docker-compose.yml (o compose.yml). En lugar de escribir largos comandos docker run con decenas de flags, defines toda la infraestructura en un archivo declarativo.
Las ventajas principales son:
- Reproducibilidad: cualquier persona puede levantar el mismo entorno con un solo comando
- Versionado: el archivo se guarda en el repositorio junto con el codigo
- Simplicidad: un comando
docker compose upreemplaza decenas de comandos individuales - Aislamiento: cada proyecto tiene su propia red y volumenes independientes
docker compose (sin guion) es el comando integrado. El antiguo docker-compose (con guion) es la version legacy en Python. En este articulo usaremos la version moderna docker compose.
Estructura del archivo docker-compose.yml
Un archivo Compose tiene una estructura clara con secciones principales:
1# docker-compose.yml
2services:
3 # Definicion de cada contenedor
4 web:
5 image: nginx:alpine
6 ports:
7 - "8080:80"
8
9 api:
10 build: ./backend
11 ports:
12 - "3000:3000"
13 environment:
14 - NODE_ENV=production
15
16 db:
17 image: postgres:16
18 volumes:
19 - db_data:/var/lib/postgresql/data
20 environment:
21 POSTGRES_PASSWORD: secreto123
22
23# Volumenes persistentes
24volumes:
25 db_data:
26
27# Redes personalizadas (opcional)
28networks:
29 frontend:
30 backend:
Las secciones principales son:
| Seccion | Descripcion |
|---|---|
services |
Define cada contenedor de la aplicacion |
volumes |
Volumenes nombrados para persistir datos |
networks |
Redes personalizadas para comunicacion entre servicios |
Servicios: configuracion detallada
Cada servicio dentro de services puede usar una imagen existente o construir una desde un Dockerfile. Veamos las opciones mas comunes:
1services:
2 mi-servicio:
3 # Opcion 1: usar imagen existente
4 image: node:20-alpine
5
6 # Opcion 2: construir desde Dockerfile
7 build:
8 context: ./mi-app
9 dockerfile: Dockerfile.prod
10
11 # Puertos expuestos (host:contenedor)
12 ports:
13 - "3000:3000"
14
15 # Variables de entorno
16 environment:
17 - DATABASE_URL=postgresql://user:pass@db:5432/mydb
18 - NODE_ENV=production
19
20 # O desde un archivo .env
21 env_file:
22 - .env
23
24 # Volumenes
25 volumes:
26 - ./src:/app/src # bind mount para desarrollo
27 - node_modules:/app/node_modules # volumen nombrado
28
29 # Redes
30 networks:
31 - backend
32
33 # Comando personalizado
34 command: npm run start:dev
35
36 # Reinicio automatico
37 restart: unless-stopped
38
39 # Limites de recursos
40 deploy:
41 resources:
42 limits:
43 memory: 512M
44 cpus: "0.5"
db, tu app se conecta a db:5432 en lugar de localhost:5432.
Redes y volumenes en Compose
Docker Compose crea automaticamente una red por defecto para todos los servicios del proyecto. Sin embargo, puedes crear redes personalizadas para aislar grupos de servicios:
1services:
2 frontend:
3 image: nginx:alpine
4 networks:
5 - red-publica
6
7 api:
8 build: ./api
9 networks:
10 - red-publica
11 - red-interna
12
13 db:
14 image: postgres:16
15 networks:
16 - red-interna
17 volumes:
18 - datos_postgres:/var/lib/postgresql/data
19
20 redis:
21 image: redis:7-alpine
22 networks:
23 - red-interna
24 volumes:
25 - datos_redis:/data
26
27networks:
28 red-publica:
29 driver: bridge
30 red-interna:
31 driver: bridge
32 internal: true # sin acceso a internet
33
34volumes:
35 datos_postgres:
36 datos_redis:
En este ejemplo, el servicio db y redis solo son accesibles desde api, nunca desde frontend. La red red-interna con internal: true ademas bloquea el acceso a internet desde esos contenedores.
Variables de entorno y archivos .env
Docker Compose soporta multiples formas de inyectar variables de entorno:
1# docker-compose.yml
2services:
3 api:
4 image: mi-api:latest
5 # Forma 1: inline
6 environment:
7 - DB_HOST=db
8 - DB_PORT=5432
9 - DB_NAME=${POSTGRES_DB} # interpolacion desde el shell o .env
10
11 # Forma 2: archivo externo
12 env_file:
13 - .env
14 - .env.local # sobreescribe valores de .env
El archivo .env se carga automaticamente si esta en el mismo directorio que docker-compose.yml:
1# .env
2POSTGRES_DB=mi_aplicacion
3POSTGRES_USER=admin
4POSTGRES_PASSWORD=SuperSecreto123
5API_PORT=3000
6NODE_ENV=production
.env al repositorio. Agrega .env a tu .gitignore y proporciona un archivo .env.example con las variables necesarias pero sin valores sensibles.
depends_on y healthchecks
El orden de inicio es crucial. Si tu API se levanta antes que la base de datos, fallara al intentar conectarse. Docker Compose ofrece depends_on para controlar dependencias:
1services:
2 db:
3 image: postgres:16
4 environment:
5 POSTGRES_PASSWORD: secreto
6 healthcheck:
7 test: ["CMD-SHELL", "pg_isready -U postgres"]
8 interval: 5s
9 timeout: 5s
10 retries: 5
11 start_period: 10s
12
13 redis:
14 image: redis:7-alpine
15 healthcheck:
16 test: ["CMD", "redis-cli", "ping"]
17 interval: 5s
18 timeout: 3s
19 retries: 5
20
21 api:
22 build: ./api
23 depends_on:
24 db:
25 condition: service_healthy
26 redis:
27 condition: service_healthy
28 environment:
29 DATABASE_URL: postgresql://postgres:secreto@db:5432/postgres
30 REDIS_URL: redis://redis:6379
Sin la opcion condition: service_healthy, Docker solo espera a que el contenedor inicie, no a que el servicio dentro del contenedor este listo. Con healthchecks, Compose espera hasta que la base de datos realmente acepte conexiones.
depends_on sin condition: service_healthy no garantiza que el servicio dependiente este listo. Solo garantiza que el contenedor se inicio. Siempre configura healthchecks para bases de datos y servicios criticos.
Comandos esenciales de Docker Compose
Estos son los comandos que usaras a diario:
1# Levantar todos los servicios (modo detached)
2docker compose up -d
3
4# Levantar y forzar rebuild de imagenes
5docker compose up -d --build
6
7# Ver logs de todos los servicios
8docker compose logs
9
10# Ver logs de un servicio especifico en tiempo real
11docker compose logs -f api
12
13# Ver el estado de los servicios
14docker compose ps
15
16# Detener todos los servicios
17docker compose down
18
19# Detener y eliminar volumenes (CUIDADO: borra datos)
20docker compose down -v
21
22# Ejecutar un comando en un servicio en ejecucion
23docker compose exec api sh
24
25# Escalar un servicio (crear multiples instancias)
26docker compose up -d --scale api=3
27
28# Ver la configuracion final (util para debug)
29docker compose config
30
31# Reiniciar un servicio especifico
32docker compose restart api
33
34# Pull de las imagenes mas recientes
35docker compose pull
| Comando | Descripcion |
|---|---|
up -d |
Levanta servicios en segundo plano |
down |
Detiene y elimina contenedores y redes |
logs -f |
Muestra logs en tiempo real |
ps |
Lista servicios y su estado |
exec |
Ejecuta un comando dentro de un servicio |
down -v |
Detiene y elimina todo, incluyendo volumenes |
Proyecto practico: WordPress + MySQL + phpMyAdmin
Vamos a crear un stack completo de WordPress con su base de datos y un panel de administracion para la base de datos. Este es un ejemplo real que puedes usar para desarrollo local.
Crea un directorio para el proyecto y el archivo docker-compose.yml:
1mkdir wordpress-stack && cd wordpress-stack
1# docker-compose.yml
2services:
3 # Base de datos MySQL
4 mysql:
5 image: mysql:8.0
6 container_name: wp-mysql
7 restart: unless-stopped
8 environment:
9 MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
10 MYSQL_DATABASE: ${MYSQL_DATABASE}
11 MYSQL_USER: ${MYSQL_USER}
12 MYSQL_PASSWORD: ${MYSQL_PASSWORD}
13 volumes:
14 - mysql_data:/var/lib/mysql
15 networks:
16 - wp-network
17 healthcheck:
18 test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p${MYSQL_ROOT_PASSWORD}"]
19 interval: 10s
20 timeout: 5s
21 retries: 5
22 start_period: 30s
23
24 # WordPress
25 wordpress:
26 image: wordpress:6-apache
27 container_name: wp-app
28 restart: unless-stopped
29 depends_on:
30 mysql:
31 condition: service_healthy
32 ports:
33 - "8080:80"
34 environment:
35 WORDPRESS_DB_HOST: mysql:3306
36 WORDPRESS_DB_USER: ${MYSQL_USER}
37 WORDPRESS_DB_PASSWORD: ${MYSQL_PASSWORD}
38 WORDPRESS_DB_NAME: ${MYSQL_DATABASE}
39 WORDPRESS_TABLE_PREFIX: wp_
40 volumes:
41 - wp_html:/var/www/html
42 - ./wp-content/themes:/var/www/html/wp-content/themes
43 - ./wp-content/plugins:/var/www/html/wp-content/plugins
44 networks:
45 - wp-network
46
47 # phpMyAdmin para administrar la base de datos
48 phpmyadmin:
49 image: phpmyadmin:5
50 container_name: wp-phpmyadmin
51 restart: unless-stopped
52 depends_on:
53 mysql:
54 condition: service_healthy
55 ports:
56 - "8081:80"
57 environment:
58 PMA_HOST: mysql
59 PMA_PORT: 3306
60 MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
61 networks:
62 - wp-network
63
64# Volumenes persistentes
65volumes:
66 mysql_data:
67 name: wp-mysql-data
68 wp_html:
69 name: wp-html-data
70
71# Red del proyecto
72networks:
73 wp-network:
74 name: wordpress-network
75 driver: bridge
Ahora crea el archivo de variables de entorno:
1# .env
2MYSQL_ROOT_PASSWORD=R00tP@ssw0rd2026
3MYSQL_DATABASE=wordpress_db
4MYSQL_USER=wp_user
5MYSQL_PASSWORD=WpP@ssw0rd2026
Levanta el stack completo:
1# Levantar todo
2docker compose up -d
3
4# Verificar que todo esta corriendo
5docker compose ps
6
7# Ver los logs de MySQL hasta que este listo
8docker compose logs -f mysql
9
10# Acceder a WordPress: http://localhost:8080
11# Acceder a phpMyAdmin: http://localhost:8081
themes y plugins permiten desarrollar temas y plugins directamente en tu maquina local. Los cambios se reflejan inmediatamente en WordPress sin necesidad de reconstruir el contenedor.
Buenas practicas con Docker Compose
Para mantener tus archivos Compose limpios y profesionales, sigue estas recomendaciones:
- Nombra tus contenedores con
container_namepara identificarlos facilmente en los logs - Usa
.envpara secretos y nunca hardcodees contrasenas en el YAML - Configura healthchecks para todas las bases de datos y servicios criticos
- Usa
restart: unless-stoppedpara que los servicios se recuperen de caidas - Nombra volumenes y redes con
name:para evitar prefijos automaticos - Separa redes para que los servicios solo se comuniquen con quienes necesiten
- Usa perfiles (
profiles) para herramientas opcionales como phpMyAdmin o debug tools
1# Ejemplo de perfiles: phpMyAdmin solo se levanta con --profile debug
2services:
3 phpmyadmin:
4 image: phpmyadmin:5
5 profiles:
6 - debug
7 ports:
8 - "8081:80"
9
10# Levantar sin phpMyAdmin:
11# docker compose up -d
12# Levantar con phpMyAdmin:
13# docker compose --profile debug up -d
Resumen y siguiente articulo
En este articulo aprendimos todo lo necesario para trabajar con Docker Compose:
- Que es Docker Compose y por que simplifica la gestion de aplicaciones multi-contenedor
- Estructura del archivo
docker-compose.yml: services, volumes, networks - Configuracion de servicios: imagenes, build, puertos, variables de entorno
- Redes y volumenes para comunicacion y persistencia de datos
- depends_on con healthchecks para control de orden de inicio
- Comandos esenciales: up, down, logs, ps, exec
- Proyecto practico: stack completo de WordPress + MySQL + phpMyAdmin
Comments
Sign in to leave a comment
No comments yet. Be the first!