Docker Compose: el día que dejé de levantar contenedores a mano
Tenía un script Bash con 8 comandos docker run. Cada vez que alguien del equipo necesitaba levantar el entorno de desarrollo, le mandaba el script por Slack y rezaba para que no hubiera cambiado nada desde la última vez. Un día un compañero me mostró su docker-compose.yml. Nunca más volví al script.
¿Qué es Docker Compose?
Docker Compose es una herramienta para definir y ejecutar aplicaciones multi-contenedor usando un archivo YAML. En lugar de recordar 8 comandos docker run con todos sus flags, definís todos los servicios, redes y volúmenes en un solo archivo versionado. Un comando levanta todo; otro lo baja.
El docker-compose.yml completo: .NET + PostgreSQL + Redis + Nginx
Este es el stack que uso como base en mis proyectos. Cada servicio tiene su rol claro:
version: '3.8'
services:
# Proxy inverso - único punto de entrada
nginx:
image: nginx:1.25-alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- ./nginx/certs:/etc/nginx/certs:ro
depends_on:
- api
restart: unless-stopped
# API .NET 8
api:
build:
context: .
dockerfile: Dockerfile
environment:
- ASPNETCORE_ENVIRONMENT=Production
- ConnectionStrings__Default=Host=postgres;Database=miapp;Username=app;Password=${DB_PASSWORD}
- Redis__ConnectionString=redis:6379
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_started
restart: unless-stopped
# Sin -p: solo accesible internamente a través de nginx
# Base de datos PostgreSQL
postgres:
image: postgres:16-alpine
environment:
POSTGRES_DB: miapp
POSTGRES_USER: app
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- postgres-data:/var/lib/postgresql/data
- ./sql/init.sql:/docker-entrypoint-initdb.d/init.sql:ro
healthcheck:
test: ["CMD-SHELL", "pg_isready -U app -d miapp"]
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped
# Cache Redis
redis:
image: redis:7-alpine
command: redis-server --appendonly yes --requirepass ${REDIS_PASSWORD}
volumes:
- redis-data:/data
restart: unless-stopped
volumes:
postgres-data:
redis-data:
networks:
default:
name: miapp-network
El archivo .env: secretos fuera del YAML
# .env (en .gitignore - nunca en el repo)
DB_PASSWORD=password-super-seguro-aqui
REDIS_PASSWORD=otro-password-seguro
Los comandos que uso todos los días
# Levantar todo en background
docker compose up -d
# Levantar y ver los logs mientras arranca
docker compose up
# Levantar solo un servicio (y sus dependencias)
docker compose up -d api
# Ver estado de los servicios
docker compose ps
# Logs de todos los servicios
docker compose logs -f
# Logs de un servicio específico
docker compose logs -f api
# Ejecutar comando en un servicio
docker compose exec api bash
docker compose exec postgres psql -U app -d miapp
# Bajar todo (mantiene volúmenes)
docker compose down
# Bajar y eliminar volúmenes (¡CUIDADO en producción!)
docker compose down -v
# Rebuild y restart de un servicio
docker compose up -d --build api
# Escalar un servicio (múltiples instancias)
docker compose up -d --scale api=3
Health checks: que Compose espere a que los servicios estén listos
Uno de los problemas clásicos: la API arranca antes que la base de datos y falla al conectar. La solución está en los healthcheck y depends_on con condición, como hice en el ejemplo de Postgres. Compose espera hasta que el healthcheck pase antes de arrancar los servicios dependientes.
# Verificar el healthcheck de un servicio
docker compose ps
# NAME STATUS
# miapp-postgres-1 healthy ← Postgres superó el healthcheck
# miapp-api-1 running ← API arrancó después
El antes y el después
Mi script Bash antes:
# ❌ Lo que tenía antes (8 líneas que siempre tenía que recordar actualizar)
docker network create miapp
docker run -d --name postgres --network miapp -e POSTGRES_PASSWORD=... ...
docker run -d --name redis --network miapp ...
docker run -d --name api --network miapp -e DB_HOST=postgres ...
docker run -d --name nginx --network miapp -p 80:80 ...
# etc...
Ahora:
# ✅ Todo el stack en un comando
docker compose up -d
El archivo está en el repo. Cualquier miembro del equipo puede clonar y levantar el entorno completo en un comando. Sin documentación de «cómo levantar el entorno». Sin scripts que se desactualizan. El docker-compose.yml es la documentación.
← Artículo anterior: Redes en Docker | Serie Docker Completo | Próximo: Entornos consistentes →
← Artículo anterior: Redes en Docker: de ‘no puedo conectar mis contenedores’ a entenderlo de verdad | Serie Docker Completo | Próximo: Cómo uso Docker para tener el mismo entorno en dev, test y producción →

Dejar un comentario
¿Quieres unirte a la conversación?Siéntete libre de contribuir!