Cómo uso Docker para tener el mismo entorno en dev, test y producción
El mismo código, el mismo docker-compose.yml, la misma imagen. Pero en desarrollo quiero hot reload, logs verbosos y la base de datos con datos de prueba. En producción quiero imágenes optimizadas, variables reales y sin herramientas de debug. Docker tiene una forma elegante de manejar esto sin duplicar archivos.
El problema clásico: dev funciona, prod falla
Antes de adoptar este patrón, tenía dos docker-compose.yml separados: uno para dev y otro para prod. Se desincronizaban constantemente. Cambiaba algo en dev, me olvidaba de replicarlo en prod, y el deploy fallaba. La solución es usar un archivo base más overrides por entorno.
El patrón: base + override
# docker-compose.yml (base - lo que es igual en todos los entornos)
version: '3.8'
services:
api:
image: ${REGISTRY:-localhost}/mi-api:${TAG:-latest}
environment:
- ASPNETCORE_ENVIRONMENT=${APP_ENV:-Development}
- ConnectionStrings__Default=${DB_CONNECTION}
depends_on:
postgres:
condition: service_healthy
restart: unless-stopped
postgres:
image: postgres:16-alpine
environment:
POSTGRES_DB: miapp
POSTGRES_USER: app
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- postgres-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U app"]
interval: 10s
retries: 5
volumes:
postgres-data:
# docker-compose.override.yml (desarrollo - se aplica automáticamente)
version: '3.8'
services:
api:
build:
context: .
dockerfile: Dockerfile.dev # SDK completo con hot reload
volumes:
- .:/app # bind mount para hot reload
ports:
- "5000:80" # expuesto para debuggear
environment:
- ASPNETCORE_ENVIRONMENT=Development
- Logging__LogLevel__Default=Debug
postgres:
ports:
- "5432:5432" # accesible desde el host para pgAdmin
volumes:
- ./sql/seed-dev.sql:/docker-entrypoint-initdb.d/seed.sql:ro
# docker-compose.prod.yml (producción - aplicar explícitamente)
version: '3.8'
services:
api:
image: miregistry.local/mi-api:${TAG} # imagen pre-buildeada
deploy:
resources:
limits:
memory: 512m
cpus: '0.5'
environment:
- ASPNETCORE_ENVIRONMENT=Production
- Logging__LogLevel__Default=Warning
# Sin ports expuestos - solo accesible via nginx
nginx:
image: nginx:1.25-alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/nginx.prod.conf:/etc/nginx/nginx.conf:ro
Cómo usarlos
# Desarrollo (usa base + override automáticamente)
docker compose up -d
# Producción (base + prod explícito)
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d
# Staging (si tuviera un docker-compose.staging.yml)
docker compose -f docker-compose.yml -f docker-compose.staging.yml up -d
Variables de entorno por entorno: los archivos .env
# .env.dev (en el repo - no tiene secretos reales)
APP_ENV=Development
DB_PASSWORD=dev-password
DB_CONNECTION=Host=postgres;Database=miapp;Username=app;Password=dev-password
REGISTRY=localhost
TAG=latest
# .env.prod (NUNCA en el repo - en el servidor o secrets manager)
APP_ENV=Production
DB_PASSWORD=password-super-seguro-generado
DB_CONNECTION=Host=postgres;Database=miapp;Username=app;Password=password-super-seguro-generado
REGISTRY=miregistry.intranet.empresa.com
TAG=1.4.2
# Desarrollo
docker compose --env-file .env.dev up -d
# Producción
docker compose --env-file .env.prod -f docker-compose.yml -f docker-compose.prod.yml up -d
Resultado: el mismo repo, comportamiento correcto en cada entorno
Con este patrón, un desarrollador nuevo puede clonar el repo y levantar el entorno de desarrollo con docker compose up -d. El pipeline de CI/CD usa el archivo de producción con las variables correctas. No hay dos versiones del mismo archivo que se desincronicen. La infraestructura está en el código, versionada junto con la aplicación.
En mi cluster SUSE, el deploy de producción es un script de 4 líneas: pull del repo, pull de la imagen nueva, docker compose up -d con el .env.prod, y verificación de health. Predecible, repetible, auditable.
← Artículo anterior: Docker Compose | Serie Docker Completo | Próximo: Docker en CI/CD →
← Artículo anterior: Docker Compose: el día que dejé de levantar contenedores a mano | Serie Docker Completo | Próximo: Docker en mi pipeline de CI/CD: builds reproducibles sin sorpresas →

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