docker-compose up es el primer comando que aprendes. Lo que viene después — networking, secretos, healthchecks, perfiles para distintos entornos — es lo que separa una configuración funcional de una lista para producción.
Table of contents
Open Table of contents
Estructura base limpia
name: mi-app
services:
api:
build:
context: .
dockerfile: Dockerfile
target: production # multi-stage target
environment:
NODE_ENV: production
env_file: .env.production # nunca hardcodees credenciales
ports:
- "3000:3000"
depends_on:
db:
condition: service_healthy # espera a que DB esté lista
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
Multi-stage builds: menos MB, más seguridad
Un Dockerfile de producción nunca debería incluir las herramientas de desarrollo:
# Stage 1: dependencias y build
FROM node:22-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci # [!code highlight]
COPY . .
RUN npm run build
# Stage 2: imagen final mínima
FROM node:22-alpine AS production # [!code ++]
WORKDIR /app # [!code ++]
# Solo copiamos lo necesario
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
USER node # no corras como root // [!code ++]
EXPOSE 3000
CMD ["node", "dist/server.js"]Dockerfile
La diferencia en tamaño puede ser de 600 MB → 80 MB.
Perfiles para distintos entornos
Con profiles puedes activar servicios según el contexto sin mantener múltiples archivos Compose:
services:
api:
# sin profile = siempre activo
build: .
adminer:
image: adminer
profiles: [dev, debug] # solo en dev
ports:
- "8080:8080"
prometheus:
image: prom/prometheus
profiles: [monitoring] # solo cuando lo necesites
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.ymlcompose.yml
# Solo levantar API + DB
docker compose up
# Levantar con herramientas de dev
docker compose --profile dev up
# Todo el stack de monitoreo
docker compose --profile monitoring up
Healthchecks que realmente funcionan
El depends_on básico sólo espera a que el contenedor arranque, 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 # tiempo de gracia inicial
worker:
build: .
depends_on:
redis:
condition: service_healthy # espera el healthcheck verdecompose.yml
Networking: aislamiento por defecto
Cada compose.yml crea una red propia. Para comunicar stacks separados:
networks:
frontend:
driver: bridge
backend:
driver: bridge
internal: true # sin acceso a internet
services:
nginx:
networks: [frontend, backend] # el único que toca ambas redes
api:
networks: [backend] # aislado del exterior
db:
networks: [backend] # idemcompose.yml
Lista de verificación antes de producción
- Variables sensibles en
secretso.envfuera del repositorio - Multi-stage build activo
-
restart: unless-stoppeden todos los servicios críticos - Healthchecks configurados con
start_periodadecuado -
depends_onconcondition: service_healthy - Usuarios no-root en los contenedores (
USER node,USER app) - Volúmenes nombrados para datos persistentes (no bind mounts en prod)
-
--max-old-space-sizeconfigurado según la memoria del contenedor
La diferencia entre un
compose.ymlde tutorial y uno de producción no está en el número de líneas — está en saber qué puede fallar y haberlo contemplado.