Despliegue en producción con Docker Compose: 7 estrategias clave desde health checks hasta actualizaciones sin tiempo de inactividad
"Funciona en mi máquina" — El cementerio de contenedores en producción
El mantra de todo desarrollador: "Funciona en mi máquina." Pero cuando los contenedores llegan a producción, la verdadera pesadilla comienza:
- Contenedores silenciosamente OOM Killed, dejando solo
Out of memoryen los logs - Base de datos aún no lista, contenedores de la aplicación gritando
Connection refused - Contenedor se cae a las 3 AM sin política de reinicio — servicio caído hasta la mañana
- Archivos de log llenando discos,
docker logsmostrando decenas de GB de texto no estructurado - Credenciales de producción en texto plano dentro de
docker-compose.yml, contraseñas de base de datos expuestas
Si todavía usas docker compose up -d como toda tu estrategia de producción, este artículo es para ti.
Referencia de conceptos clave
| Concepto | Propósito | Configuración clave en producción |
|---|---|---|
| Health Check | Detectar si un contenedor está realmente listo | healthcheck + depends_on.condition |
| Límites de recursos | Limitar CPU/memoria, prevenir consumo excesivo | deploy.resources.limits |
| Política de reinicio | Reinicio automático ante salida anormal | deploy.restart_policy |
| Secrets | Almacenamiento cifrado para datos sensibles | secrets + Docker Secret |
| Driver de logging | Logging estructurado + rotación de logs | logging.driver + logging.options |
| Profiles | Inicio selectivo de servicios por entorno | profiles |
| Watch | Sincronización automática de cambios de archivos a contenedores | watch (Compose Watch) |
5 Desafíos de producción
Desafío 1: Orden de inicio de contenedores incontrolable
La base de datos todavía se está inicializando mientras el contenedor de la aplicación intenta conectarse, causando fallos de inicio. depends_on solo garantiza el orden de inicio, no la disponibilidad del servicio.
Desafío 2: Expansión ilimitada de recursos
Contenedores sin límites de recursos son como autos sin frenos. Un solo contenedor con fuga de memoria puede consumir toda la memoria del host y derribar todos los servicios.
Desafío 3: El agujero negro de los logs
El driver de log json-file por defecto no rota. Después de 3 meses de ejecución, /var/lib/docker llena el disco y todos los servicios se caen.
Desafío 4: Exposición de datos sensibles
Contraseñas de base de datos en bloques environment en texto plano, archivos .env subidos a Git, claves API hardcodeadas en imágenes — estas son bombas de tiempo para incidentes de producción.
Desafío 5: Las actualizaciones significan tiempo de inactividad
docker compose up -d detiene los contenedores antiguos antes de iniciar los nuevos por defecto, haciendo que el servicio no esté disponible durante las actualizaciones. Para servicios 24/7, esto es inaceptable.
7 Patrones de producción
Patrón 1: Health checks y ordenamiento de dependencias
Problema: depends_on solo controla el orden de inicio, no garantiza la disponibilidad del servicio.
Solución: Usar healthcheck + depends_on.condition: service_healthy.
services:
postgres:
image: postgres:16-alpine
environment:
POSTGRES_DB: appdb
POSTGRES_USER: appuser
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
secrets:
- db_password
healthcheck:
test: ["CMD-SHELL", "pg_isready -U appuser -d appdb"]
interval: 5s
timeout: 3s
retries: 5
start_period: 10s
volumes:
- postgres_data:/var/lib/postgresql/data
redis:
image: redis:7-alpine
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 3s
retries: 5
app:
image: myapp:latest
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
ports:
- "3000:3000"
Parámetros clave:
interval: Intervalo de verificación, se recomienda 5-10 segundos para produccióntimeout: Tiempo de espera de una sola verificación, se recomienda 3-5 segundosretries: Fallos consecutivos antes de marcar como unhealthystart_period: Período de gracia después del inicio del contenedor, los fallos no cuentan hacia los reintentos
Patrón 2: Límites de recursos y protección OOM
Problema: Contenedores sin límites compiten por los recursos del host. Un contenedor descontrolado puede derribar toda la máquina.
Solución: Usar deploy.resources para establecer límites y reservas de CPU y memoria.
services:
app:
image: myapp:latest
deploy:
resources:
limits:
cpus: '2.0'
memory: 512M
reservations:
cpus: '0.5'
memory: 256M
restart_policy:
condition: on-failure
delay: 5s
max_attempts: 3
window: 120s
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval: 10s
timeout: 5s
retries: 3
worker:
image: myworker:latest
deploy:
resources:
limits:
cpus: '1.0'
memory: 1G
reservations:
memory: 512M
Límites vs Reservas:
limits: Techo duro, si se excede → OOM Kill o throttling de CPUreservations: Garantía suave, el planificador intenta cumplirla pero no la impone
Estrategia de protección OOM:
services:
critical-service:
image: critical-app:latest
deploy:
resources:
limits:
memory: 1G
cap_add:
- SYS_PTRACE
A nivel del host, configura vm.overcommit_memory y ajusta las políticas OOM:
# Verificar OOM Score del contenedor
docker inspect --format='{{.State.OOMKilled}}' <container_id>
# Establecer política OOM del host: no matar procesos críticos
echo -1000 > /proc/<pid>/oom_score_adj
Patrón 3: Logging estructurado y rotación de logs
Problema: El driver de log json-file por defecto no rota; los discos se llenan con el tiempo.
Solución: Configurar driver de log + política de rotación. Se recomienda el driver local para producción.
services:
app:
image: myapp:latest
logging:
driver: local
options:
max-size: "10m"
max-file: "5"
tag: "{{.Name}}/{{.ID}}"
nginx:
image: nginx:1.27-alpine
logging:
driver: json-file
options:
max-size: "50m"
max-file: "10"
tag: "nginx/{{.Name}}"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
Comparación de drivers de log:
| Driver | Caso de uso | Ventajas | Desventajas |
|---|---|---|---|
| local | Predeterminado en producción | Auto-rotación, almacenamiento comprimido | Solo local |
| json-file | Se necesitan logs JSON estructurados | Soporte nativo de Docker | Requiere configuración manual de rotación |
| syslog | Recolección centralizada de logs | Puede enviar a remoto | Configuración compleja |
| fluentd | Integración con stack EFK | Enrutamiento flexible de logs | Requiere despliegue de Fluentd |
Logging estructurado a nivel de aplicación:
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
ENV TZ=UTC
ENV LOG_FORMAT=json
CMD ["node", "server.js"]
const logger = {
info: (msg, meta = {}) => {
console.log(JSON.stringify({ level: 'info', msg, ts: new Date().toISOString(), ...meta }));
},
error: (msg, meta = {}) => {
console.error(JSON.stringify({ level: 'error', msg, ts: new Date().toISOString(), ...meta }));
}
};
Patrón 4: Gestión de Secrets
Problema: Datos sensibles almacenados en texto plano en archivos compose o variables de entorno.
Solución: Docker Secrets + variables de entorno con sufijo _FILE.
secrets:
db_password:
file: ./secrets/db_password.txt
api_key:
file: ./secrets/api_key.txt
jwt_secret:
file: ./secrets/jwt_secret.txt
services:
postgres:
image: postgres:16-alpine
environment:
POSTGRES_DB: appdb
POSTGRES_USER: appuser
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
secrets:
- db_password
app:
image: myapp:latest
environment:
DATABASE_URL: postgresql://appuser:${DB_PASSWORD}@postgres:5432/appdb
API_KEY_FILE: /run/secrets/api_key
JWT_SECRET_FILE: /run/secrets/jwt_secret
secrets:
- api_key
- jwt_secret
Gestión de archivos de secrets:
# Crear directorio de secrets
mkdir -p secrets
chmod 700 secrets
# Escribir archivos de secrets
echo "my-super-secret-password-2026" > secrets/db_password.txt
echo "ak-live-xxxx-yyyy-zzzz" > secrets/api_key.txt
echo "jwt-hs256-secret-key-here" > secrets/jwt_secret.txt
# Establecer permisos: solo legible por root
chmod 600 secrets/*.txt
.gitignore debe incluir:
secrets/
*.secret
.env.production
.env.staging
Comparación Docker Secrets vs .env:
| Característica | Docker Secrets | Archivos .env |
|---|---|---|
| Almacenamiento cifrado | Sí (en modo Swarm) | No |
| Permisos de archivo | Restringidos (/run/secrets/) | Depende del filesystem |
| Pista de auditoría | Sí | No |
| Sincronización entre nodos | Swarm sincroniza automáticamente | Distribución manual |
| Caso de uso | Swarm/producción de un solo host | Desarrollo |
Patrón 5: Actualizaciones rolling sin tiempo de inactividad
Problema: docker compose up -d detiene los contenedores antiguos antes de iniciar los nuevos por defecto.
Solución: Usar docker compose up --no-down + health checks + proxy inverso.
services:
app:
image: myapp:${APP_VERSION:-latest}
deploy:
replicas: 2
update_config:
parallelism: 1
delay: 10s
order: start-first
failure_action: rollback
rollback_config:
parallelism: 0
order: stop-first
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval: 5s
timeout: 3s
retries: 3
start_period: 15s
labels:
- "com.toolsku.app=true"
ports:
- "3000-3001:3000"
nginx:
image: nginx:1.27-alpine
ports:
- "80:80"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
depends_on:
app:
condition: service_healthy
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:80/health"]
interval: 10s
timeout: 3s
retries: 3
Configuración de proxy inverso Nginx:
upstream app_backend {
server app:3000;
}
server {
listen 80;
server_name app.example.com;
location / {
proxy_pass http://app_backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_next_upstream error timeout http_502 http_503;
}
location /health {
access_log off;
return 200 'ok';
add_header Content-Type text/plain;
}
}
Script de actualización sin tiempo de inactividad:
#!/bin/bash
set -euo pipefail
NEW_IMAGE="myapp:v2.0.0"
echo "🚀 Iniciando actualización sin tiempo de inactividad a ${NEW_IMAGE}"
# 1. Descargar nueva imagen
docker compose pull app
# 2. Iniciar nuevos contenedores (sin detener los antiguos)
docker compose up -d --no-deps --scale app=2 app
# 3. Esperar a que los nuevos contenedores estén saludables
echo "⏳ Esperando a que los nuevos contenedores estén saludables..."
sleep 15
# 4. Verificar salud del nuevo contenedor
for i in $(seq 1 30); do
if curl -sf http://localhost:3000/health > /dev/null 2>&1; then
echo "✅ El nuevo contenedor está saludable"
break
fi
if [ $i -eq 30 ]; then
echo "❌ Health check fallido, revirtiendo..."
docker compose up -d --no-deps --scale app=1 app
exit 1
fi
sleep 2
done
# 5. Reducir a 1 réplica
docker compose up -d --no-deps --scale app=1 app
echo "🎉 Actualización completada exitosamente"
Patrón 6: Stack de monitoreo Prometheus + Grafana
Problema: Ejecutar producción sin monitoreo es volar a ciegas — solo te enteras de los problemas por las quejas de los usuarios.
Solución: Desplegar un stack completo de monitoreo Prometheus + Grafana.
services:
prometheus:
image: prom/prometheus:v2.52.0
container_name: prometheus
restart: unless-stopped
ports:
- "9090:9090"
volumes:
- ./monitoring/prometheus.yml:/etc/prometheus/prometheus.yml:ro
- prometheus_data:/prometheus
command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.retention.time=30d'
- '--storage.tsdb.retention.size=5GB'
deploy:
resources:
limits:
memory: 512M
networks:
- monitoring
grafana:
image: grafana/grafana:11.0.0
container_name: grafana
restart: unless-stopped
ports:
- "3001:3000"
environment:
GF_SECURITY_ADMIN_USER: admin
GF_SECURITY_ADMIN_PASSWORD_FILE: /run/secrets/grafana_password
secrets:
- grafana_password
volumes:
- grafana_data:/var/lib/grafana
deploy:
resources:
limits:
memory: 256M
depends_on:
- prometheus
networks:
- monitoring
node-exporter:
image: prom/node-exporter:v1.8.0
container_name: node-exporter
restart: unless-stopped
ports:
- "9100:9100"
volumes:
- /proc:/host/proc:ro
- /sys:/host/sys:ro
- /:/rootfs:ro
command:
- '--path.procfs=/host/proc'
- '--path.sysfs=/host/sys'
- '--path.rootfs=/rootfs'
networks:
- monitoring
alertmanager:
image: prom/alertmanager:v0.27.0
container_name: alertmanager
restart: unless-stopped
ports:
- "9093:9093"
volumes:
- ./monitoring/alertmanager.yml:/etc/alertmanager/alertmanager.yml:ro
networks:
- monitoring
secrets:
grafana_password:
file: ./secrets/grafana_password.txt
volumes:
prometheus_data:
grafana_data:
networks:
monitoring:
driver: bridge
Configuración de Prometheus:
# monitoring/prometheus.yml
global:
scrape_interval: 15s
evaluation_interval: 15s
rule_files:
- "alert_rules.yml"
alerting:
alertmanagers:
- static_configs:
- targets:
- alertmanager:9093
scrape_configs:
- job_name: 'prometheus'
static_configs:
- targets: ['localhost:9090']
- job_name: 'node-exporter'
static_configs:
- targets: ['node-exporter:9100']
- job_name: 'app'
static_configs:
- targets: ['app:3000']
metrics_path: /metrics
Reglas de alertas:
# monitoring/alert_rules.yml
groups:
- name: container_alerts
rules:
- alert: ContainerDown
expr: up == 0
for: 1m
labels:
severity: critical
annotations:
summary: "Container {{ $labels.instance }} is down"
- alert: HighMemoryUsage
expr: process_resident_memory_bytes / (1024 * 1024) > 400
for: 5m
labels:
severity: warning
annotations:
summary: "Memory usage exceeds 400MB on {{ $labels.instance }}"
- alert: DiskSpaceLow
expr: node_filesystem_avail_bytes / node_filesystem_size_bytes < 0.1
for: 5m
labels:
severity: critical
annotations:
summary: "Disk space below 10% on {{ $labels.instance }}"
Patrón 7: Configuración multi-entorno (dev/staging/prod)
Problema: Las configuraciones de dev, staging y producción están mezcladas — cambiar la configuración de un entorno corre el riesgo de afectar a otros.
Solución: Usar docker-compose.override.yml + estrategia de superposición multi-archivo.
Estructura de directorios:
project/
├── docker-compose.yml # Configuración base
├── docker-compose.override.yml # Sobrescritura de dev (cargada automáticamente)
├── docker-compose.staging.yml # Sobrescritura de staging
├── docker-compose.prod.yml # Sobrescritura de producción
├── .env # Variables de entorno predeterminadas
├── .env.staging # Variables de entorno de staging
├── .env.prod # Variables de entorno de producción
├── monitoring/
│ ├── prometheus.yml
│ └── alertmanager.yml
└── secrets/
├── db_password.txt
├── api_key.txt
└── grafana_password.txt
Configuración base docker-compose.yml:
services:
app:
image: myapp:${APP_VERSION:-latest}
environment:
NODE_ENV: ${NODE_ENV:-development}
DATABASE_URL: postgresql://appuser:${DB_PASSWORD}@postgres:5432/appdb
REDIS_URL: redis://redis:6379
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
networks:
- app-network
postgres:
image: postgres:16-alpine
environment:
POSTGRES_DB: appdb
POSTGRES_USER: appuser
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
secrets:
- db_password
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U appuser -d appdb"]
interval: 5s
timeout: 3s
retries: 5
networks:
- app-network
redis:
image: redis:7-alpine
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 3s
retries: 5
networks:
- app-network
secrets:
db_password:
file: ./secrets/db_password.txt
volumes:
postgres_data:
networks:
app-network:
driver: bridge
Sobrescritura de dev docker-compose.override.yml (cargada automáticamente):
services:
app:
build: .
volumes:
- .:/app
- /app/node_modules
ports:
- "3000:3000"
- "9229:9229"
environment:
NODE_ENV: development
LOG_LEVEL: debug
deploy:
resources:
limits:
memory: 512M
command: node --inspect=0.0.0.0:9229 server.js
adminer:
image: adminer:latest
ports:
- "8080:8080"
networks:
- app-network
Sobrescritura de producción docker-compose.prod.yml:
services:
app:
image: myapp:${APP_VERSION}
ports:
- "3000:3000"
environment:
NODE_ENV: production
LOG_LEVEL: info
deploy:
replicas: 2
resources:
limits:
cpus: '2.0'
memory: 512M
reservations:
cpus: '0.5'
memory: 256M
restart_policy:
condition: on-failure
delay: 5s
max_attempts: 3
logging:
driver: local
options:
max-size: "10m"
max-file: "5"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval: 10s
timeout: 5s
retries: 3
start_period: 15s
postgres:
deploy:
resources:
limits:
cpus: '2.0'
memory: 2G
reservations:
memory: 1G
logging:
driver: local
options:
max-size: "50m"
max-file: "10"
redis:
deploy:
resources:
limits:
memory: 512M
command: redis-server --appendonly yes --maxmemory 400mb --maxmemory-policy allkeys-lru
volumes:
- redis_data:/data
logging:
driver: local
options:
max-size: "10m"
max-file: "3"
nginx:
image: nginx:1.27-alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.prod.conf:/etc/nginx/nginx.conf:ro
depends_on:
app:
condition: service_healthy
deploy:
resources:
limits:
memory: 128M
logging:
driver: local
options:
max-size: "50m"
max-file: "10"
volumes:
redis_data:
Comandos de inicio:
# Desarrollo (carga override automáticamente)
docker compose up -d
# Staging
docker compose -f docker-compose.yml -f docker-compose.staging.yml --env-file .env.staging up -d
# Producción
docker compose -f docker-compose.yml -f docker-compose.prod.yml --env-file .env.prod up -d
5 Errores comunes
Error 1: depends_on no significa que el servicio esté listo
❌ Incorrecto:
services:
app:
depends_on:
- postgres
# El contenedor postgres se inició, pero la BD puede no estar inicializada aún
✅ Correcto:
services:
app:
depends_on:
postgres:
condition: service_healthy
postgres:
healthcheck:
test: ["CMD-SHELL", "pg_isready -U appuser -d appdb"]
interval: 5s
timeout: 3s
retries: 5
start_period: 10s
Error 2: Sin límites de recursos
❌ Incorrecto:
services:
app:
image: myapp:latest
# Sin límites de recursos — una fuga de memoria puede consumir todo el host
✅ Correcto:
services:
app:
image: myapp:latest
deploy:
resources:
limits:
cpus: '2.0'
memory: 512M
reservations:
cpus: '0.25'
memory: 128M
Error 3: Sin rotación de logs
❌ Incorrecto:
services:
app:
image: myapp:latest
# Driver json-file por defecto, los logs crecen indefinidamente
✅ Correcto:
services:
app:
image: myapp:latest
logging:
driver: local
options:
max-size: "10m"
max-file: "5"
Error 4: Datos sensibles en texto plano
❌ Incorrecto:
services:
postgres:
environment:
POSTGRES_PASSWORD: "my-secret-password-123"
# Contraseña en texto plano en el archivo compose — desastre si se sube a Git
✅ Correcto:
services:
postgres:
environment:
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
secrets:
- db_password
secrets:
db_password:
file: ./secrets/db_password.txt
Error 5: Usar el tag latest
❌ Incorrecto:
services:
app:
image: myapp:latest
# Cada pull puede obtener una imagen diferente — no es reproducible
✅ Correcto:
services:
app:
image: myapp:2.1.0
# O usar una variable para control de versiones
image: myapp:${APP_VERSION:-2.1.0}
Referencia de solución de errores
| Mensaje de error | Causa | Solución |
|---|---|---|
OOMKilled |
El contenedor excedió el límite de memoria | Aumentar límite memory u optimizar uso de memoria de la app |
Connection refused |
Servicio dependiente no listo | Agregar healthcheck + depends_on.condition |
no space left on device |
Logs/imagenes llenando el disco | Configurar rotación logging + docker system prune |
Bucle de restarting |
La app falla al iniciar | Verificar docker logs <id> y confirmar configuración |
permission denied |
Problema de permisos de archivo/directorio | Verificar directiva user y permisos de volúmenes |
port is already allocated |
Conflicto de puertos | Cambiar mapeo de puertos o detener proceso en conflicto |
Estado unhealthy |
Health check fallando | Verificar que el comando healthcheck sea correcto |
secret not found |
Archivo de secret faltante | Asegurar que el archivo correspondiente exista en secrets/ |
Cannot connect to the Docker daemon |
Docker no está ejecutándose | systemctl start docker |
image pulling failed |
Fallo al descargar imagen | Verificar red/autenticación de registry/ortografía del nombre de imagen |
Optimización avanzada
Docker Compose Watch para desarrollo
Compose Watch sincroniza automáticamente los cambios de archivos a los contenedores sin reconstruir imágenes:
services:
app:
build: .
develop:
watch:
- action: sync
path: ./src
target: /app/src
- action: rebuild
path: ./package.json
- action: sync+restart
path: ./config
target: /app/config
# Iniciar modo watch
docker compose watch
Aislamiento de red y seguridad
services:
app:
networks:
- frontend
- backend
postgres:
networks:
- backend
# postgres no está en la red frontend — acceso externo bloqueado
nginx:
networks:
- frontend
ports:
- "80:80"
networks:
frontend:
driver: bridge
backend:
driver: bridge
internal: true
# internal:true deshabilita el acceso externo
Optimización de imágenes con multi-stage builds
# ---- Etapa de build ----
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# ---- Etapa de runtime ----
FROM node:20-alpine AS runner
WORKDIR /app
RUN addgroup -g 1001 -S appgroup && \
adduser -S appuser -u 1001 -G appgroup
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./
USER appuser
EXPOSE 3000
HEALTHCHECK --interval=10s --timeout=3s --retries=3 \
CMD curl -f http://localhost:3000/health || exit 1
CMD ["node", "dist/server.js"]
Comparación de herramientas de orquestación
| Característica | Docker Compose | Kubernetes | Nomad | Docker Swarm |
|---|---|---|---|---|
| Complejidad | Baja | Alta | Media | Baja |
| Despliegue de un solo host | ✅ Excelente | ❌ Excesivo | ⚠️ Utilizable | ✅ Excelente |
| Orquestación multi-host | ❌ No soportado | ✅ Capacidad central | ✅ Capacidad central | ⚠️ Básico |
| Auto-scaling | ❌ | ✅ HPA/VPA | ✅ | ⚠️ Manual |
| Actualizaciones rolling | ⚠️ Requiere scripts | ✅ Nativo | ✅ Nativo | ✅ Nativo |
| Service discovery | ⚠️ DNS | ✅ CoreDNS | ✅ Consul | ✅ DNS |
| Orquestación de almacenamiento | ❌ | ✅ CSI | ✅ CSI | ⚠️ Básico |
| Curva de aprendizaje | Baja | Alta | Media | Baja |
| Escala adecuada | 1-10 servicios | 100+ servicios | 50+ servicios | 10-50 servicios |
| Listo para producción | ✅ Un solo host | ✅ Gran escala | ✅ Mediana a gran escala | ⚠️ Comunidad en declive |
Recomendación: Usa Docker Compose para producción de un solo host/pequeña escala, Kubernetes para gran escala, Nomad si estás en el ecosistema HashiCorp. Docker Swarm está cada vez más marginado — no recomendado para nuevos proyectos.
Resumen
El despliegue en producción con Docker Compose no es solo
docker compose up -d. Los health checks aseguran que los servicios estén realmente listos, los límites de recursos previenen avalanchas OOM, la rotación de logs evita el agotamiento del disco, los Secrets protegen datos sensibles, las actualizaciones sin tiempo de inactividad garantizan disponibilidad 24/7, los stacks de monitoreo eliminan puntos ciegos, y las configuraciones multi-entorno mantienen dev/staging/prod correctamente separados. Domina estas 7 estrategias, y Docker Compose es totalmente capaz de despliegues de producción a pequeña y mediana escala.
Herramientas recomendadas
- Formateador JSON - Formatear configuraciones JSON relacionadas con Docker Compose YAML
- Codificación Base64 - Codificar Secrets y datos de configuración sensibles
- Calculadora de Hash - Generar checksums para archivos de configuración y asegurar consistencia en los despliegues
Prueba estas herramientas que se ejecutan en tu navegador — no requieren registro →