Docker Compose: Guía Completa con Ejemplos Prácticos
Docker Compose simplifica la orquestación de aplicaciones multi-contenedor al definir y ejecutar aplicaciones complejas usando un único archivo de configuración YAML. Esta guía completa cubre todo desde conceptos básicos hasta despliegues de producción avanzados con ejemplos del mundo real.
Tabla de Contenidos
- Introducción a Docker Compose
- Requisitos Previos
- Conceptos Básicos de Docker Compose
- Estructura del Archivo Compose
- Configuración de Servicios
- Redes en Compose
- Volúmenes en Compose
- Variables de Entorno
- Comandos de Docker Compose
- Ejemplos del Mundo Real
- Mejores Prácticas de Producción
- Solución de Problemas
- Conclusión
Introducción a Docker Compose
Docker Compose es una herramienta para definir y ejecutar aplicaciones Docker multi-contenedor. En lugar de gestionar contenedores individualmente con extensos comandos docker run, Compose usa un archivo YAML para configurar todos los servicios, redes y volúmenes de la aplicación en un solo lugar.
¿Por Qué Usar Docker Compose?
- Gestión Simplificada: Define el stack completo de la aplicación en un archivo
- Reproducibilidad: Comparte configuraciones con tu equipo
- Eficiencia de Desarrollo: Inicia el stack completo con un solo comando
- Consistencia de Entorno: Misma configuración en dev, test y staging
- Dependencias de Servicios: Define orden de inicio y dependencias
- Escalado Fácil: Escala servicios hacia arriba o abajo con un comando
Docker Compose vs Kubernetes
- Docker Compose: Desarrollo de host único y despliegues pequeños
- Kubernetes: Orquestación de producción multi-host a escala
Requisitos Previos
Antes de comenzar, asegúrate de tener:
- Docker Engine instalado (versión 20.10 o superior)
- Docker Compose instalado (versión 2.0 o superior)
- Conocimiento básico de Docker (imágenes, contenedores, volúmenes)
- Editor de texto para archivos YAML
- Comprensión de la arquitectura de tu aplicación
Verifica la instalación:
# Check Docker Compose version
docker compose version
# Or older standalone version
docker-compose --version
Notas de Instalación
Docker Compose v2 ahora está integrado como plugin de Docker CLI:
# New syntax (recommended)
docker compose up
# Old syntax (standalone)
docker-compose up
Esta guía usa la nueva sintaxis docker compose.
Conceptos Básicos de Docker Compose
Creando Tu Primer Archivo Compose
Crea un archivo llamado docker-compose.yml:
version: '3.8'
services:
web:
image: nginx:alpine
ports:
- "8080:80"
Inicia la aplicación:
docker compose up
Detén la aplicación:
docker compose down
Flujo de Trabajo Básico
# Start services
docker compose up -d
# View running services
docker compose ps
# View logs
docker compose logs
# Stop services
docker compose down
Estructura del Archivo Compose
Especificación de Versión
# Compose file format version
version: '3.8'
Nota: La versión 3.8 es ampliamente soportada. Versiones más nuevas (3.9, 3.10) agregan características.
Claves de Nivel Superior
version: '3.8'
services: # Define containers
service1:
service2:
networks: # Define networks
network1:
volumes: # Define volumes
volume1:
configs: # Define configs (Swarm mode)
config1:
secrets: # Define secrets (Swarm mode)
secret1:
Ejemplo Mínimo
version: '3.8'
services:
web:
image: nginx:alpine
ports:
- "80:80"
Configuración de Servicios
Servicios Basados en Imagen
services:
database:
image: postgres:15-alpine
environment:
POSTGRES_PASSWORD: secret
ports:
- "5432:5432"
Servicios Basados en Construcción
services:
app:
build: . # Build from current directory
ports:
- "3000:3000"
api:
build:
context: ./api # Build context
dockerfile: Dockerfile.prod # Custom Dockerfile
args: # Build arguments
NODE_ENV: production
Nomenclatura de Contenedores
services:
web:
container_name: my-web-server
image: nginx:alpine
Mapeo de Puertos
services:
web:
image: nginx
ports:
- "8080:80" # host:container
- "8443:443"
- "127.0.0.1:9000:9000" # bind to specific interface
- "3000-3005:3000-3005" # port range
Variables de Entorno
services:
app:
image: node:18-alpine
environment:
NODE_ENV: production
API_KEY: ${API_KEY} # From host environment
DATABASE_URL: postgres://db:5432/mydb
Archivos de Entorno
services:
app:
image: node:18-alpine
env_file:
- .env
- .env.production
Archivo .env:
NODE_ENV=production
API_KEY=your_api_key_here
DATABASE_URL=postgres://db:5432/mydb
Volúmenes
services:
app:
image: node:18-alpine
volumes:
- ./app:/usr/src/app # bind mount
- app-data:/app/data # named volume
- /var/run/docker.sock:/var/run/docker.sock:ro # read-only
Depends On
services:
web:
image: nginx
depends_on:
- api
- cache
api:
image: node:18-alpine
depends_on:
- database
database:
image: postgres:15-alpine
cache:
image: redis:alpine
Políticas de Reinicio
services:
web:
image: nginx
restart: always # always, no, on-failure, unless-stopped
Verificaciones de Salud
services:
web:
image: nginx
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
Límites de Recursos
services:
app:
image: myapp
deploy:
resources:
limits:
cpus: '2'
memory: 1G
reservations:
cpus: '0.5'
memory: 512M
Command y Entrypoint
services:
app:
image: node:18-alpine
command: npm start
# Or with array syntax
command: ["npm", "run", "dev"]
worker:
image: node:18-alpine
entrypoint: /docker-entrypoint.sh
Directorio de Trabajo
services:
app:
image: node:18-alpine
working_dir: /usr/src/app
Usuario
services:
app:
image: node:18-alpine
user: "1000:1000"
Redes en Compose
Red Predeterminada
Docker Compose crea automáticamente una red predeterminada:
version: '3.8'
services:
web:
image: nginx
api:
image: node:18-alpine
Los servicios pueden comunicarse usando nombres de servicios como nombres de host.
Redes Personalizadas
version: '3.8'
services:
web:
image: nginx
networks:
- frontend
api:
image: node:18-alpine
networks:
- frontend
- backend
database:
image: postgres
networks:
- backend
networks:
frontend:
backend:
Configuración de Red
networks:
frontend:
driver: bridge
backend:
driver: bridge
ipam:
config:
- subnet: 172.28.0.0/16
gateway: 172.28.0.1
Redes Externas
networks:
existing-network:
external: true
name: my-pre-existing-network
Volúmenes en Compose
Volúmenes Nombrados
version: '3.8'
services:
database:
image: postgres:15-alpine
volumes:
- db-data:/var/lib/postgresql/data
volumes:
db-data:
Configuración de Volumen
volumes:
db-data:
driver: local
driver_opts:
type: none
device: /path/on/host
o: bind
Volúmenes Externos
volumes:
existing-volume:
external: true
name: my-pre-existing-volume
Variables de Entorno
Sustitución de Variables
services:
web:
image: nginx:${NGINX_VERSION:-latest}
ports:
- "${WEB_PORT:-8080}:80"
Archivo .env
Crea .env en el mismo directorio que docker-compose.yml:
NGINX_VERSION=alpine
WEB_PORT=8080
POSTGRES_PASSWORD=secretpassword
Múltiples Archivos de Entorno
services:
app:
image: myapp
env_file:
- ./common.env
- ./prod.env
Comandos de Docker Compose
Iniciar Servicios
# Start all services
docker compose up
# Start in detached mode
docker compose up -d
# Start specific services
docker compose up web database
# Force recreate containers
docker compose up --force-recreate
# Build images before starting
docker compose up --build
Detener Servicios
# Stop all services
docker compose stop
# Stop specific service
docker compose stop web
# Stop and remove containers, networks
docker compose down
# Remove volumes too
docker compose down -v
# Remove images too
docker compose down --rmi all
Ver Estado
# List running services
docker compose ps
# Show all services (including stopped)
docker compose ps -a
# View service logs
docker compose logs
# Follow logs
docker compose logs -f
# Logs for specific service
docker compose logs -f web
# Show last 100 lines
docker compose logs --tail=100
Ejecutar Comandos
# Execute command in service
docker compose exec web sh
# Run one-off command
docker compose run web npm install
# Run without dependencies
docker compose run --no-deps web npm test
Construir Imágenes
# Build all services
docker compose build
# Build specific service
docker compose build web
# Build without cache
docker compose build --no-cache
# Build with parallel execution
docker compose build --parallel
Escalar Servicios
# Scale specific service
docker compose up -d --scale web=3
# Scale multiple services
docker compose up -d --scale web=3 --scale worker=5
Validación
# Validate compose file
docker compose config
# Render compose file with variables
docker compose config --resolve-image-digests
Otros Comandos Útiles
# Pause services
docker compose pause
# Unpause services
docker compose unpause
# Restart services
docker compose restart
# View resource usage
docker compose top
# Pull latest images
docker compose pull
Ejemplos del Mundo Real
Ejemplo 1: Stack WordPress
version: '3.8'
services:
wordpress:
image: wordpress:latest
container_name: wordpress
restart: unless-stopped
ports:
- "8080:80"
environment:
WORDPRESS_DB_HOST: database
WORDPRESS_DB_USER: wordpress
WORDPRESS_DB_PASSWORD: ${DB_PASSWORD}
WORDPRESS_DB_NAME: wordpress
volumes:
- wordpress-data:/var/www/html
depends_on:
- database
networks:
- wp-network
database:
image: mysql:8.0
container_name: wordpress-db
restart: unless-stopped
environment:
MYSQL_DATABASE: wordpress
MYSQL_USER: wordpress
MYSQL_PASSWORD: ${DB_PASSWORD}
MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
volumes:
- db-data:/var/lib/mysql
networks:
- wp-network
phpmyadmin:
image: phpmyadmin:latest
container_name: phpmyadmin
restart: unless-stopped
ports:
- "8081:80"
environment:
PMA_HOST: database
MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
depends_on:
- database
networks:
- wp-network
volumes:
wordpress-data:
db-data:
networks:
wp-network:
driver: bridge
Ejemplo 2: Aplicación Full Stack Node.js
version: '3.8'
services:
frontend:
build:
context: ./frontend
dockerfile: Dockerfile
container_name: react-frontend
restart: unless-stopped
ports:
- "3000:3000"
environment:
- REACT_APP_API_URL=http://localhost:5000
volumes:
- ./frontend:/app
- /app/node_modules
networks:
- app-network
backend:
build:
context: ./backend
dockerfile: Dockerfile
container_name: node-backend
restart: unless-stopped
ports:
- "5000:5000"
environment:
- NODE_ENV=production
- DATABASE_URL=postgresql://postgres:${DB_PASSWORD}@database:5432/myapp
- REDIS_URL=redis://redis:6379
volumes:
- ./backend:/app
- /app/node_modules
depends_on:
- database
- redis
networks:
- app-network
database:
image: postgres:15-alpine
container_name: postgres-db
restart: unless-stopped
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_DB: myapp
volumes:
- postgres-data:/var/lib/postgresql/data
networks:
- app-network
redis:
image: redis:7-alpine
container_name: redis-cache
restart: unless-stopped
ports:
- "6379:6379"
volumes:
- redis-data:/data
networks:
- app-network
nginx:
image: nginx:alpine
container_name: nginx-proxy
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- ./nginx/ssl:/etc/nginx/ssl:ro
depends_on:
- frontend
- backend
networks:
- app-network
volumes:
postgres-data:
redis-data:
networks:
app-network:
driver: bridge
Ejemplo 3: Microservicios con Cola de Mensajes
version: '3.8'
services:
api-gateway:
build: ./api-gateway
ports:
- "8080:8080"
environment:
- USER_SERVICE_URL=http://user-service:3001
- ORDER_SERVICE_URL=http://order-service:3002
depends_on:
- user-service
- order-service
networks:
- microservices
user-service:
build: ./user-service
environment:
- DATABASE_URL=postgresql://postgres:${DB_PASSWORD}@postgres:5432/users
- RABBITMQ_URL=amqp://rabbitmq:5672
depends_on:
- postgres
- rabbitmq
networks:
- microservices
order-service:
build: ./order-service
environment:
- DATABASE_URL=postgresql://postgres:${DB_PASSWORD}@postgres:5432/orders
- RABBITMQ_URL=amqp://rabbitmq:5672
depends_on:
- postgres
- rabbitmq
networks:
- microservices
postgres:
image: postgres:15-alpine
environment:
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- postgres-data:/var/lib/postgresql/data
- ./init-db.sql:/docker-entrypoint-initdb.d/init.sql
networks:
- microservices
rabbitmq:
image: rabbitmq:3-management-alpine
ports:
- "5672:5672"
- "15672:15672"
environment:
RABBITMQ_DEFAULT_USER: admin
RABBITMQ_DEFAULT_PASS: ${RABBITMQ_PASSWORD}
volumes:
- rabbitmq-data:/var/lib/rabbitmq
networks:
- microservices
volumes:
postgres-data:
rabbitmq-data:
networks:
microservices:
driver: bridge
Ejemplo 4: Aplicación Python Django
version: '3.8'
services:
web:
build: .
command: gunicorn myproject.wsgi:application --bind 0.0.0.0:8000
volumes:
- .:/code
- static-volume:/code/staticfiles
- media-volume:/code/media
expose:
- 8000
environment:
- DEBUG=0
- SECRET_KEY=${SECRET_KEY}
- DATABASE_URL=postgresql://postgres:${DB_PASSWORD}@db:5432/myapp
- REDIS_URL=redis://redis:6379/1
depends_on:
- db
- redis
networks:
- django-network
nginx:
image: nginx:alpine
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- static-volume:/code/staticfiles:ro
- media-volume:/code/media:ro
ports:
- "80:80"
depends_on:
- web
networks:
- django-network
db:
image: postgres:15-alpine
volumes:
- postgres-data:/var/lib/postgresql/data
environment:
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=${DB_PASSWORD}
- POSTGRES_DB=myapp
networks:
- django-network
redis:
image: redis:7-alpine
networks:
- django-network
celery:
build: .
command: celery -A myproject worker -l info
volumes:
- .:/code
environment:
- DATABASE_URL=postgresql://postgres:${DB_PASSWORD}@db:5432/myapp
- REDIS_URL=redis://redis:6379/1
depends_on:
- db
- redis
networks:
- django-network
celery-beat:
build: .
command: celery -A myproject beat -l info
volumes:
- .:/code
environment:
- DATABASE_URL=postgresql://postgres:${DB_PASSWORD}@db:5432/myapp
- REDIS_URL=redis://redis:6379/1
depends_on:
- db
- redis
networks:
- django-network
volumes:
postgres-data:
static-volume:
media-volume:
networks:
django-network:
driver: bridge
Ejemplo 5: Stack de Monitoreo (Prometheus + Grafana)
version: '3.8'
services:
prometheus:
image: prom/prometheus:latest
container_name: prometheus
restart: unless-stopped
volumes:
- ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml
- prometheus-data:/prometheus
command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.path=/prometheus'
ports:
- "9090:9090"
networks:
- monitoring
grafana:
image: grafana/grafana:latest
container_name: grafana
restart: unless-stopped
environment:
- GF_SECURITY_ADMIN_USER=admin
- GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_PASSWORD}
- GF_INSTALL_PLUGINS=grafana-piechart-panel
volumes:
- grafana-data:/var/lib/grafana
- ./grafana/provisioning:/etc/grafana/provisioning
ports:
- "3000:3000"
depends_on:
- prometheus
networks:
- monitoring
node-exporter:
image: prom/node-exporter:latest
container_name: node-exporter
restart: unless-stopped
volumes:
- /proc:/host/proc:ro
- /sys:/host/sys:ro
- /:/rootfs:ro
command:
- '--path.procfs=/host/proc'
- '--path.sysfs=/host/sys'
- '--collector.filesystem.mount-points-exclude=^/(sys|proc|dev|host|etc)($$|/)'
ports:
- "9100:9100"
networks:
- monitoring
cadvisor:
image: gcr.io/cadvisor/cadvisor:latest
container_name: cadvisor
restart: unless-stopped
volumes:
- /:/rootfs:ro
- /var/run:/var/run:ro
- /sys:/sys:ro
- /var/lib/docker/:/var/lib/docker:ro
ports:
- "8080:8080"
networks:
- monitoring
volumes:
prometheus-data:
grafana-data:
networks:
monitoring:
driver: bridge
Mejores Prácticas de Producción
Usar Control de Versiones
Siempre confirma docker-compose.yml en git:
git add docker-compose.yml .env.example
git commit -m "Add Docker Compose configuration"
Archivos Específicos de Entorno
# docker-compose.yml (base)
version: '3.8'
services:
web:
image: myapp
# docker-compose.override.yml (development)
version: '3.8'
services:
web:
volumes:
- .:/app
command: npm run dev
# docker-compose.prod.yml (production)
version: '3.8'
services:
web:
restart: unless-stopped
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
Ejecuta con configuración específica:
# Development (uses override automatically)
docker compose up
# Production
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d
Mejores Prácticas de Seguridad
services:
app:
image: myapp
# Run as non-root user
user: "1000:1000"
# Read-only root filesystem
read_only: true
tmpfs:
- /tmp
# Drop capabilities
cap_drop:
- ALL
cap_add:
- NET_BIND_SERVICE
# Security options
security_opt:
- no-new-privileges:true
Límites de Recursos
services:
app:
image: myapp
deploy:
resources:
limits:
cpus: '2'
memory: 2G
reservations:
cpus: '1'
memory: 1G
Verificaciones de Salud
services:
app:
image: myapp
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval: 30s
timeout: 5s
retries: 3
start_period: 40s
Configuración de Logging
services:
app:
image: myapp
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
labels: "production"
Usar Secrets (Docker Swarm)
version: '3.8'
services:
app:
image: myapp
secrets:
- db_password
- api_key
secrets:
db_password:
file: ./secrets/db_password.txt
api_key:
external: true
Solución de Problemas
Ver Logs de Servicio
# All services
docker compose logs
# Specific service
docker compose logs web
# Follow logs
docker compose logs -f
# Last 100 lines
docker compose logs --tail=100
El Contenedor No Inicia
# Check status
docker compose ps
# View logs
docker compose logs service-name
# Validate compose file
docker compose config
# Force recreate
docker compose up --force-recreate
Problemas de Red
# Inspect network
docker network ls
docker network inspect project_default
# Recreate network
docker compose down
docker compose up
Problemas de Permisos de Volumen
# Check volume
docker volume inspect project_volume-name
# Fix permissions in container
docker compose exec service-name chown -R user:group /path
Limpiar Recursos
# Stop and remove everything
docker compose down
# Remove volumes too
docker compose down -v
# Remove images
docker compose down --rmi all
# Complete cleanup
docker compose down -v --rmi all --remove-orphans
Depurar Servicio
# Execute shell in running container
docker compose exec service-name sh
# Run one-off command
docker compose run --rm service-name sh
# View container processes
docker compose top service-name
Conclusión
Docker Compose optimiza la gestión de aplicaciones multi-contenedor, haciéndolo esencial para flujos de trabajo de desarrollo modernos. Esta guía cubrió todo desde conceptos básicos hasta configuraciones listas para producción.
Conclusiones Clave
- Configuración Única: Define el stack completo en
docker-compose.yml - Dependencias de Servicios: Usa
depends_onpara orden de inicio - Aislamiento de Red: Creación automática de red con descubrimiento de servicios
- Gestión de Volúmenes: Persiste datos con volúmenes nombrados y bind
- Flexibilidad de Entorno: Usa archivos
.envy sustitución de variables - Listo para Producción: Implementa verificaciones de salud, logging y límites de recursos
Referencia Rápida
# Essential Commands
docker compose up -d # Start services
docker compose down # Stop services
docker compose ps # List services
docker compose logs -f # View logs
docker compose exec web sh # Access shell
docker compose build # Build images
docker compose pull # Pull images
docker compose restart # Restart services
# Management
docker compose config # Validate file
docker compose up --build # Rebuild and start
docker compose down -v # Remove with volumes
docker compose scale web=3 # Scale service
Próximos Pasos
- Practica: Crea archivos compose para tus proyectos
- Optimiza: Implementa construcciones multi-etapa en Dockerfiles
- Asegura: Agrega verificaciones de salud y opciones de seguridad
- Monitorea: Integra soluciones de logging y monitoreo
- Orquesta: Avanza a Kubernetes para producción
- Automatiza: Integra con pipelines CI/CD
- Documenta: Mantén README completo con instrucciones de configuración
Docker Compose es perfecto para entornos de desarrollo y despliegues de host único. Para orquestación de producción multi-host, considera Kubernetes o Docker Swarm.


