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 →

0 comentarios

Dejar un comentario

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

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.