Saltar al contenido

Docker Compose en 2026: buenas prácticas que realmente importan

Andrés Ujpán
Fecha de publicación:
2 min de lectura

docker-compose up es el primer comando que aprendes. Lo que viene después — redes, secretos, healthchecks, perfiles para diferentes entornos — es lo que separa una configuración funcional de una lista para producción.

Tabla de contenido

Estructura base limpia

name: my-app

services:
  api:
    build:
      context: .
      dockerfile: Dockerfile
      target: production # multi-stage target
    environment:
      NODE_ENV: production
    env_file: .env.production # never hardcode credentials
    ports:
      - "3000:3000"
    depends_on:
      db:
        condition: service_healthy # wait for DB to be ready
    restart: unless-stopped

  db:
    image: postgres:17-alpine
    volumes:
      - postgres_data:/var/lib/postgresql/data
    environment:
      POSTGRES_PASSWORD_FILE: /run/secrets/db_password
    secrets:
      - db_password
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 10s
      timeout: 5s
      retries: 5
    restart: unless-stopped

volumes:
  postgres_data:

secrets:
  db_password:
    file: ./secrets/db_password.txtcompose.yml

Compilaciones multi-stage: menos MBs, más seguridad

Un Dockerfile de producción nunca debería incluir herramientas de desarrollo:

# Stage 1: dependencies and build
FROM node:22-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci                    # [!code highlight]
COPY . .
RUN npm run build

# Stage 2: minimal final image
FROM node:22-alpine AS production  # [!code ++]
WORKDIR /app                       # [!code ++]
# Only copy what's necessary       
COPY --from=builder /app/dist ./dist  
COPY --from=builder /app/node_modules ./node_modules  
USER node                          # do not run as root // [!code ++]
EXPOSE 3000
CMD ["node", "dist/server.js"]Dockerfile

La diferencia de tamaño puede ser de 600 MB → 80 MB.

Perfiles para diferentes entornos

Con profiles puedes activar servicios según el contexto sin tener que mantener múltiples archivos de Compose:

services:
  api:
    # no profile = always active
    build: .

  adminer:
    image: adminer
    profiles: [dev, debug] # only in dev
    ports:
      - "8080:8080"

  prometheus:
    image: prom/prometheus
    profiles: [monitoring] # only when you need it
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.ymlcompose.yml
# Only bring up API + DB
docker compose up

# Bring up with dev tools
docker compose --profile dev up

# Entire monitoring stack
docker compose --profile monitoring up

Healthchecks que realmente funcionan

El depends_on básico solo espera a que el contenedor se inicie, no a que el servicio esté listo. La diferencia importa:

services:
  redis:
    image: redis:7-alpine
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 5s
      timeout: 3s
      retries: 10
      start_period: 10s # initial grace period

  worker:
    build: .
    depends_on:
      redis:
        condition: service_healthy # wait for green healthcheckcompose.yml

Redes: aislamiento por defecto

Cada compose.yml crea su propia red. Para comunicar stacks independientes:

networks:
  frontend:
    driver: bridge
  backend:
    driver: bridge
    internal: true # no internet access

services:
  nginx:
    networks: [frontend, backend] # the only one touching both networks

  api:
    networks: [backend] # isolated from the outside

  db:
    networks: [backend] # dittocompose.yml

Lista de verificación antes de producción

  • Variables sensibles en secrets o .env fuera del repositorio
  • Compilación multi-stage activa
  • restart: unless-stopped en todos los servicios críticos
  • Healthchecks configurados con el start_period adecuado
  • depends_on con condition: service_healthy
  • Usuarios no root en los contenedores (USER node, USER app)
  • Volúmenes con nombre para datos persistentes (sin bind mounts en producción)
  • --max-old-space-size configurado de acuerdo con la memoria del contenedor

La diferencia entre un compose.yml de tutorial y uno de producción no está en el número de líneas, sino en saber qué puede fallar y haberlo tenido en cuenta.

Artículo Anterior
Vibe Coding: programar con IA a la velocidad del pensamiento
Fecha de publicación:
3 min de lectura
Lectura Recomendada
PostgreSQL y JSONB: el poder de una base de datos relacional con la flexibilidad de documentos
Fecha de publicación:
1 min de lectura
100%