← maurobernal.com.ar

Docker Compose de cero a producción — Parte 2: CLI, dependencias robustas y perfiles

Esta es la segunda parte de la serie Docker Compose de cero a producción. En la Parte 1 vimos qué es Compose y cómo estructurar el archivo compose.yaml. Ahora le toca al día a día: los comandos que vas a escribir decenas de veces, cómo hacer que las dependencias entre servicios funcionen bien de verdad, y cómo manejar servicios opcionales con perfiles.

Los comandos que más vas a usar

Ciclo de vida

# Levantar todo en segundo plano
docker compose up -d

# Levantar y forzar reconstrucción de imágenes
docker compose up -d --build

# Levantar solo servicios específicos
docker compose up -d api db

# Detener y eliminar contenedores + redes (conserva volúmenes)
docker compose down

# Detener y eliminar TODO incluyendo volúmenes (¡cuidado: borra datos!)
docker compose down -v

# Solo detener sin eliminar (los contenedores quedan, solo se apagan)
docker compose stop

# Iniciar contenedores ya existentes (sin recrear)
docker compose start

# Reiniciar servicios
docker compose restart api

La diferencia entre stop/start y down/up importa: stop preserva los contenedores existentes (más rápido para reinicios), mientras que down los elimina completamente (útil para empezar limpio).

Inspección y debugging

# Ver estado de todos los contenedores del proyecto
docker compose ps

# Ver logs de todos los servicios en tiempo real
docker compose logs -f

# Ver logs solo de un servicio, últimas 50 líneas
docker compose logs -f --tail=50 api

# Entrar a un contenedor con una shell interactiva
docker compose exec api bash
docker compose exec db psql -U myuser -d mydb

# Ejecutar un comando puntual en un contenedor nuevo (y eliminarlo al terminar)
docker compose run --rm api npm run migrate

# Ver procesos dentro de cada servicio
docker compose top

# Validar y ver la configuración final (muy útil para depurar overrides y variables)
docker compose config

docker compose config es uno de los comandos más útiles que tardé en descubrir: muestra la configuración final después de fusionar todos los archivos y reemplazar variables de entorno. Ideal para verificar que los overrides están funcionando como esperás.

Gestión de imágenes

# Construir imágenes sin levantar servicios
docker compose build

# Construir solo un servicio específico
docker compose build api

# Descargar las últimas versiones de las imágenes
docker compose pull

# Ver qué imágenes usa el proyecto
docker compose images

depends_on y healthchecks: dependencias que funcionan de verdad

Este es uno de los puntos donde más errores cometí al principio. depends_on: db solo garantiza que el contenedor de la base de datos inicie antes — no que la base de datos esté lista para aceptar conexiones. El resultado: la API arranca, intenta conectarse a Postgres que todavía está inicializándose, falla, y el contenedor muere.

La solución correcta: combinar depends_on con healthcheck.

services:
  api:
    build: .
    depends_on:
      db:
        condition: service_healthy   # Espera hasta que db esté HEALTHY, no solo running
      cache:
        condition: service_started   # Para este, alcanza con que esté corriendo

  db:
    image: postgres:15-alpine
    environment:
      POSTGRES_USER: myuser
      POSTGRES_DB: mydb
      POSTGRES_PASSWORD: mypassword
    healthcheck:
      # Comando que se ejecuta dentro del contenedor para verificar salud
      test: ["CMD-SHELL", "pg_isready -U myuser -d mydb"]
      interval: 10s      # Verificar cada 10 segundos
      timeout: 5s        # Esperar máximo 5 segundos por respuesta
      retries: 5         # Marcar como unhealthy después de 5 fallos
      start_period: 30s  # Período de gracia al inicio (no cuenta como fallo)

  cache:
    image: redis:alpine
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 5s
      timeout: 3s
      retries: 3

Las tres condiciones disponibles para depends_on:

  • service_started: el contenedor arrancó (comportamiento por defecto, no espera que la app esté lista)
  • service_healthy: el healthcheck del servicio devuelve éxito — el que más usás
  • service_completed_successfully: para tareas de inicialización que deben completarse antes de continuar (migraciones, seeds)

Ejemplo práctico de service_completed_successfully para migraciones:

services:
  migrate:
    build: .
    command: ["npm", "run", "migrate"]   # Se ejecuta una vez y termina
    depends_on:
      db:
        condition: service_healthy

  api:
    build: .
    depends_on:
      migrate:
        condition: service_completed_successfully  # La API arranca después de migrar
      db:
        condition: service_healthy

Escalado: múltiples instancias de un servicio

Compose permite correr múltiples instancias del mismo servicio con el flag --scale. Útil para simular carga durante desarrollo o para escalar workers en background.

# Levantar 3 instancias del servicio 'worker'
docker compose up -d --scale worker=3

# Escalar mientras el proyecto ya está corriendo
docker compose up -d --scale api=2 --scale worker=5

Dos cosas importantes para que el escalado funcione:

  • No uses container_name en servicios que vas a escalar (los nombres deben ser únicos)
  • No mapees puertos fijos al host — usá puertos efímeros ("3000" en lugar de "3000:3000") o rangos ("3000-3005:3000")
services:
  api:
    build: .
    ports:
      - "3000"    # Puerto efímero: Docker asigna un puerto libre del host por instancia
    # NO poner container_name si vas a escalar

Perfiles: servicios que no siempre querés levantar

Siempre hay servicios auxiliares que solo necesitás en ciertas situaciones: un gestor de base de datos visual, un servidor de correo falso para desarrollo, un servicio de métricas. Con perfiles podés tenerlos definidos en el mismo archivo pero inactivos por defecto.

services:
  api:
    build: .
    # Sin perfil = siempre activo

  db:
    image: postgres:15
    # Sin perfil = siempre activo

  # Solo activo con el perfil 'tools'
  pgadmin:
    image: dpage/pgadmin4
    profiles: ["tools"]
    ports:
      - "5050:80"
    environment:
      PGADMIN_DEFAULT_EMAIL: admin@local.com
      PGADMIN_DEFAULT_PASSWORD: admin

  # Solo activo con el perfil 'tools'
  mailhog:
    image: mailhog/mailhog
    profiles: ["tools"]
    ports:
      - "8025:8025"   # UI web para ver emails enviados en desarrollo

  # Solo activo con el perfil 'monitoring'
  prometheus:
    image: prom/prometheus
    profiles: ["monitoring"]
# Solo servicios base (api + db)
docker compose up -d

# Servicios base + herramientas de administración
docker compose --profile tools up -d

# Todo a la vez
docker compose --profile tools --profile monitoring up -d

Uso esto constantemente para pgAdmin, Adminer, MailHog o Kibana. Están definidos, documentados y listos para usar cuando los necesito, pero no arrancan por defecto y no consumen recursos innecesariamente.

Resumen de la Parte 2

  • CLI: up/down/logs/exec/config son los comandos del día a día
  • depends_on + healthcheck: la combinación correcta para dependencias reales entre servicios
  • Escalado: --scale para múltiples instancias, sin container_name ni puertos fijos
  • Perfiles: servicios auxiliares disponibles cuando los necesitás, sin que estorben el resto

En la Parte 3 cerramos la serie con dos proyectos reales completos y las estrategias para manejar entornos de desarrollo y producción desde el mismo base de archivos.

Comentarios

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.