Gestionar contenedores Docker individuales con largos comandos docker run se vuelve rápidamente impráctico cuando tu aplicación requiere múltiples servicios interconectados. Docker Compose resuelve este problema permitiéndote definir y gestionar aplicaciones multi-contenedor usando un único archivo YAML declarativo. Para los administradores de sistemas, Docker Compose es una herramienta indispensable que cierra la brecha entre la gestión manual de contenedores y las plataformas de orquestación completas como Kubernetes.
Esta guía cubre todo lo que necesitas saber para usar Docker Compose de manera efectiva en escenarios del mundo real, desde comprender la sintaxis YAML hasta desplegar stacks listos para producción con comprobaciones de salud, límites de recursos y políticas de reinicio adecuadas.
Requisitos Previos
Antes de comenzar, asegúrate de tener:
- Docker Engine instalado en tu sistema. Si aún no lo has hecho, sigue nuestra guía: Cómo Instalar Docker en Ubuntu 22.04 y 24.04
- Comprensión básica de los conceptos de Docker (imágenes, contenedores, volúmenes, redes)
- Un editor de texto y acceso a la terminal con privilegios sudo
¿Qué Es Docker Compose?
Docker Compose es una herramienta para definir y ejecutar aplicaciones Docker multi-contenedor. En lugar de ejecutar múltiples comandos docker run con flags complejos, describes toda tu pila de aplicaciones en un archivo docker-compose.yml (o compose.yml) y luego inicias todo con un solo comando.
Un caso de uso típico podría incluir una aplicación web que depende de una base de datos, una capa de caché y un proxy inverso. Docker Compose te permite definir todos estos servicios, sus configuraciones, redes y volúmenes en un solo lugar, haciendo que tu infraestructura sea reproducible y controlable por versiones.
Compose V2 vs. V1: Qué Cambió
Docker Compose ha pasado por una evolución significativa:
| Característica | Compose V1 (Legacy) | Compose V2 (Actual) |
|---|---|---|
| Comando | docker-compose (binario separado) | docker compose (plugin CLI) |
| Lenguaje | Python | Go |
| Rendimiento | Inicio más lento | Significativamente más rápido |
| Instalación | Instalación separada requerida | Incluido con Docker Engine |
| Estado | Obsoleto (fin de vida junio 2023) | En desarrollo activo |
Si instalaste Docker siguiendo nuestra guía, el plugin Compose V2 ya está disponible como docker compose (nota el espacio en lugar del guion). Todos los ejemplos en este artículo usan la sintaxis de V2.
Importante: Si encuentras scripts o documentación que hacen referencia a
docker-compose(con guion), reemplázalo pordocker compose(con espacio). La funcionalidad es la misma, pero V1 ya no recibe mantenimiento.
Anatomía de un Archivo docker-compose.yml
Un archivo Compose tiene tres secciones principales: services, networks y volumes. Esta es la estructura básica:
# Opcional: especificar la versión del archivo Compose (no requerido en V2)
services:
# Define los servicios de tu aplicación aquí
web:
image: nginx:latest
ports:
- "80:80"
database:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: secretpassword
networks:
# Define redes personalizadas aquí (opcional)
volumes:
# Define volúmenes nombrados aquí (opcional)
Servicios
Los servicios son los contenedores que componen tu aplicación. Cada definición de servicio puede incluir:
- image o build: La imagen Docker a usar o un Dockerfile desde el cual construir
- ports: Mapeo de puertos entre el host y el contenedor
- environment: Variables de entorno
- volumes: Puntos de montaje para persistencia de datos
- depends_on: Dependencias de inicio de servicios
- restart: Política de reinicio
- networks: A qué redes conectarse
- command: Sobrescribir el comando predeterminado del contenedor
Redes
Por defecto, Docker Compose crea una única red para tu aplicación y todos los servicios pueden comunicarse entre sí usando sus nombres de servicio como nombres de host. Puedes definir redes personalizadas para un control más granular sobre la comunicación entre servicios:
services:
web:
networks:
- frontend
- backend
api:
networks:
- backend
database:
networks:
- backend
networks:
frontend:
driver: bridge
backend:
driver: bridge
internal: true # Sin acceso externo
En este ejemplo, el servicio web puede comunicarse tanto con api como con database a través de la red backend, mientras que api y database están aislados del acceso externo por el flag internal: true en la red backend. La red frontend podría usarse para el tráfico desde un proxy inverso.
Volúmenes
Los volúmenes persisten datos más allá del ciclo de vida de un contenedor. Los volúmenes nombrados son el enfoque recomendado:
services:
database:
image: mysql:8.0
volumes:
- db_data:/var/lib/mysql # Volumen nombrado
- ./init.sql:/docker-entrypoint-initdb.d/init.sql # Montaje bind
volumes:
db_data:
driver: local
Variables de Entorno y Archivos .env
Codificar secretos y valores de configuración directamente en tu docker-compose.yml es una mala práctica. Docker Compose soporta archivos .env para gestionar variables de entorno de forma limpia.
Crea un archivo .env en el mismo directorio que tu docker-compose.yml:
# .env
MYSQL_ROOT_PASSWORD=supersecretpassword
MYSQL_DATABASE=myapp
MYSQL_USER=appuser
MYSQL_PASSWORD=apppassword
NGINX_PORT=8080
Referencia estas variables en tu archivo Compose:
services:
database:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
MYSQL_DATABASE: ${MYSQL_DATABASE}
MYSQL_USER: ${MYSQL_USER}
MYSQL_PASSWORD: ${MYSQL_PASSWORD}
web:
image: nginx:latest
ports:
- "${NGINX_PORT}:80"
También puedes pasar una directiva env_file para cargar variables de entorno directamente en un contenedor:
services:
api:
image: myapp:latest
env_file:
- ./app.env
Consejo de Seguridad: Siempre añade los archivos
.enva tu.gitignorepara evitar subir secretos accidentalmente al control de versiones. Proporciona un archivo.env.examplecon valores de ejemplo para fines de documentación.
Ejemplo Práctico 1: Stack Nginx + PHP-FPM + MySQL
Este es un stack clásico de aplicación web que muchos administradores de sistemas necesitan desplegar:
services:
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/conf.d:/etc/nginx/conf.d:ro
- ./app:/var/www/html:ro
- ./nginx/ssl:/etc/nginx/ssl:ro
depends_on:
php:
condition: service_started
networks:
- webnet
restart: unless-stopped
php:
image: php:8.3-fpm-alpine
volumes:
- ./app:/var/www/html
- ./php/php.ini:/usr/local/etc/php/php.ini:ro
environment:
DB_HOST: mysql
DB_NAME: ${MYSQL_DATABASE}
DB_USER: ${MYSQL_USER}
DB_PASSWORD: ${MYSQL_PASSWORD}
depends_on:
mysql:
condition: service_healthy
networks:
- webnet
restart: unless-stopped
mysql:
image: mysql:8.0
volumes:
- mysql_data:/var/lib/mysql
- ./mysql/init:/docker-entrypoint-initdb.d:ro
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
MYSQL_DATABASE: ${MYSQL_DATABASE}
MYSQL_USER: ${MYSQL_USER}
MYSQL_PASSWORD: ${MYSQL_PASSWORD}
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
networks:
- webnet
restart: unless-stopped
networks:
webnet:
driver: bridge
volumes:
mysql_data:
Puntos clave sobre esta configuración:
- Las comprobaciones de salud en MySQL aseguran que PHP-FPM no inicie hasta que la base de datos esté realmente lista para aceptar conexiones, no solo cuando el contenedor se inicia.
- Los montajes de solo lectura (
:ro) se usan para archivos de configuración y certificados SSL como medida de seguridad. - Los volúmenes nombrados (
mysql_data) persisten los datos de la base de datos independientemente del ciclo de vida del contenedor. restart: unless-stoppedasegura que los servicios se recuperen automáticamente de fallos pero permanezcan detenidos si los detienes manualmente.
Ejemplo Práctico 2: Stack de Monitoreo con Prometheus + Grafana
Un stack de monitoreo es esencial para cualquier entorno de producción:
services:
prometheus:
image: prom/prometheus:latest
volumes:
- ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro
- prometheus_data:/prometheus
ports:
- "9090:9090"
command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.path=/prometheus'
- '--storage.tsdb.retention.time=30d'
- '--web.console.libraries=/etc/prometheus/console_libraries'
- '--web.console.templates=/etc/prometheus/consoles'
networks:
- monitoring
restart: unless-stopped
grafana:
image: grafana/grafana:latest
volumes:
- grafana_data:/var/lib/grafana
- ./grafana/provisioning:/etc/grafana/provisioning:ro
ports:
- "3000:3000"
environment:
GF_SECURITY_ADMIN_USER: ${GRAFANA_ADMIN_USER:-admin}
GF_SECURITY_ADMIN_PASSWORD: ${GRAFANA_ADMIN_PASSWORD}
GF_USERS_ALLOW_SIGN_UP: "false"
depends_on:
- prometheus
networks:
- monitoring
restart: unless-stopped
node-exporter:
image: prom/node-exporter:latest
volumes:
- /proc:/host/proc:ro
- /sys:/host/sys:ro
- /:/rootfs:ro
command:
- '--path.procfs=/host/proc'
- '--path.rootfs=/rootfs'
- '--path.sysfs=/host/sys'
- '--collector.filesystem.mount-points-exclude=^/(sys|proc|dev|host|etc)($$|/)'
networks:
- monitoring
restart: unless-stopped
cadvisor:
image: gcr.io/cadvisor/cadvisor:latest
volumes:
- /:/rootfs:ro
- /var/run:/var/run:ro
- /sys:/sys:ro
- /var/lib/docker/:/var/lib/docker:ro
- /dev/disk/:/dev/disk:ro
privileged: true
devices:
- /dev/kmsg:/dev/kmsg
networks:
- monitoring
restart: unless-stopped
networks:
monitoring:
driver: bridge
volumes:
prometheus_data:
grafana_data:
Este stack proporciona métricas a nivel de contenedor (cAdvisor), métricas a nivel de host (Node Exporter), almacenamiento y consulta de métricas (Prometheus) y paneles de visualización (Grafana).
Comandos Comunes de Docker Compose
Aquí tienes una referencia completa de los comandos que usarás con más frecuencia:
# Iniciar todos los servicios en segundo plano
docker compose up -d
# Iniciar y forzar la reconstrucción de imágenes
docker compose up -d --build
# Detener todos los servicios
docker compose down
# Detener y eliminar volúmenes (PRECAUCIÓN: destruye datos)
docker compose down -v
# Ver servicios en ejecución
docker compose ps
# Ver logs de todos los servicios
docker compose logs
# Seguir logs de un servicio específico
docker compose logs -f nginx
# Reiniciar un servicio específico
docker compose restart php
# Escalar un servicio (ejecutar múltiples instancias)
docker compose up -d --scale worker=3
# Ejecutar un comando en un contenedor de servicio en ejecución
docker compose exec mysql mysql -u root -p
# Descargar las últimas imágenes de todos los servicios
docker compose pull
# Ver la configuración resuelta de Compose
docker compose config
# Listar todos los proyectos Compose ejecutándose en el sistema
docker compose ls
Consejo: El comando
docker compose configes extremadamente útil para depuración. Muestra el YAML completamente resuelto después de la sustitución de variables, mostrándote exactamente lo que Docker Compose ejecutará.
Consideraciones para Producción
Políticas de Reinicio
Siempre establece una política de reinicio para servicios en producción:
services:
web:
restart: unless-stopped # Recomendado para la mayoría de servicios
Políticas disponibles:
no(predeterminado): Nunca reiniciaralways: Siempre reiniciar, incluso si se detiene manualmenteon-failure: Reiniciar solo si el contenedor sale con un código distinto de cerounless-stopped: Reiniciar a menos que sea detenido explícitamente por el administrador
Comprobaciones de Salud
Las comprobaciones de salud permiten a Docker saber si un servicio está realmente funcionando, no solo ejecutándose:
services:
api:
image: myapi:latest
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
Las comprobaciones de salud son usadas por las condiciones de depends_on y por Docker para determinar cuándo reiniciar contenedores no saludables.
Límites de Recursos
Evita que un solo servicio consuma todos los recursos disponibles del sistema:
services:
worker:
image: myworker:latest
deploy:
resources:
limits:
cpus: '1.0'
memory: 512M
reservations:
cpus: '0.25'
memory: 128M
Nota: Los límites de recursos bajo
deploy.resourcesfuncionan condocker compose upen versiones recientes de Compose V2. En versiones anteriores, solo se aplicaban al modo Docker Swarm.
Configuración de Logging
Configura el logging por servicio para prevenir el agotamiento del disco:
services:
web:
image: nginx:latest
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
Mejores Prácticas de Seguridad
- Nunca ejecutes contenedores como root a menos que sea absolutamente necesario. Usa la directiva
user. - Establece
read_only: trueen contenedores que no necesitan escribir en su sistema de archivos. - Elimina capacidades Linux innecesarias usando
cap_drop: [ALL]y agrega de vuelta solo las necesarias concap_add. - Usa gestión de secretos para datos sensibles en lugar de variables de entorno en texto plano en producción.
services:
api:
image: myapi:latest
read_only: true
user: "1000:1000"
cap_drop:
- ALL
cap_add:
- NET_BIND_SERVICE
tmpfs:
- /tmp
Solución de Problemas de Docker Compose
Un Servicio No Puede Conectarse a Otro Servicio
Si un servicio no puede alcanzar a otro, verifica que estén en la misma red y usa el nombre del servicio (no el nombre del contenedor) como nombre de host:
# Verificar a qué redes está conectado un servicio
docker compose exec web ping database
# Inspeccionar la red
docker network ls
docker network inspect <nombre_del_proyecto>_default
Conflictos de Puertos
Si ves bind: address already in use, otro proceso está usando ese puerto:
# Encontrar qué está usando el puerto 80
sudo lsof -i :80
# o
sudo ss -tlnp | grep :80
Cambia el mapeo de puerto del host en tu archivo Compose, por ejemplo de "80:80" a "8080:80".
Los Contenedores Siguen Reiniciándose
Revisa los logs para entender por qué un contenedor está fallando:
docker compose logs --tail=50 <nombre_del_servicio>
# Verificar el código de salida del contenedor
docker compose ps -a
Las causas comunes incluyen variables de entorno faltantes, permisos de archivo incorrectos en volúmenes montados y dependencias que no están listas.
Rendimiento Lento de Bind Mounts en macOS
Si estás desarrollando en macOS y experimentas acceso lento a archivos con bind mounts, considera usar volúmenes Docker en lugar de bind mounts para directorios grandes, o usa la opción de montaje :cached:
volumes:
- ./app:/var/www/html:cached
Conclusión
Docker Compose transforma la manera en que los administradores de sistemas despliegan y gestionan aplicaciones multi-contenedor. Al definir tu infraestructura en un archivo YAML declarativo, obtienes reproducibilidad, control de versiones y un flujo de trabajo de despliegue con un solo comando. Los ejemplos y mejores prácticas cubiertos en esta guía deberían darte una base sólida para desplegar desde stacks web simples hasta soluciones de monitoreo completas.
Para tus próximos pasos, considera explorar:
- Perfiles de Docker Compose para gestionar diferentes entornos (desarrollo, staging, producción)
- Docker Compose watch para actualizaciones automáticas de servicios durante el desarrollo
- Herramientas de orquestación como Docker Swarm o Kubernetes para despliegues multi-host
Asegúrate de que Docker esté instalado correctamente en tu sistema primero. Si aún no lo has hecho, sigue nuestra guía completa de instalación: Cómo Instalar Docker en Ubuntu 22.04 y 24.04.