Docker Compose para producción: Lo que aprendí en 3 años de uso real
Durante mucho tiempo, existió un mito persistente en los pasillos de ingeniería: «Docker Compose es solo para desarrollo; si vas a producción, necesitas Kubernetes». Después de tres años gestionando decenas de servicios críticos para GaboComputacion y mis clientes usando casi exclusivamente Docker Compose, puedo decirte con total autoridad: ese mito es falso.
No me malinterpretes, Kubernetes es una maravilla de la ingeniería, pero para el 90% de las PYMES y proyectos de mediana escala, es como intentar usar un transbordador espacial para ir a comprar pan. En estos últimos tres años, he aprendido que la simplicidad de Docker Compose, cuando se usa con disciplina y los patrones correctos, es una ventaja competitiva brutal.
En este artículo, voy a abrir mi «caja de herramientas» de Docker y compartir contigo las lecciones, los errores y las configuraciones que me permiten dormir tranquilo mientras mis contenedores corren en producción.
¿Por qué Docker Compose? La filosofía de la simplicidad
Cuando eres un IT Manager con un equipo pequeño y recursos limitados, cada onza de complejidad que añades a tu stack es una onza de riesgo. Kubernetes introduce una curva de aprendizaje inmensa y una carga operativa constante (gestión de nodos, certificados, ETCD, redes complejas).
Docker Compose, en cambio, es un solo archivo YAML. Es legible, es predecible y cualquier ingeniero con conocimientos básicos de Linux puede entenderlo en una tarde. Durante estos tres años, esta simplicidad nos ha permitido:
1. Desplegar más rápido: De la idea a producción en minutos, no días.
2. Depurar con facilidad: Si algo falla, los logs están a un comando de distancia.
3. Mantener costos bajos: No necesitamos clústeres de servidores solo para correr el plano de control (control plane).
1. Patrones de Configuración para Producción
Un docker-compose.yml de desarrollo no sirve para producción. Aquí están los ajustes «no negociables» que he implementado tras varios sustos iniciales.
Restart Policies: El guardián silencioso
En desarrollo, si un contenedor falla, simplemente lo reinicias manualmente. En producción, el sistema debe ser resiliente.
Usa siempre restart: unless-stopped. A diferencia de always, esta política evita que un contenedor que has detenido manualmente por mantenimiento intente reiniciarse infinitamente, pero garantiza que si el servidor se reinicia o el proceso falla, el servicio vuelva a estar arriba de inmediato.
Healthchecks: No confíes en el estado «Up»
Que un contenedor diga que está «Up» (corriendo) solo significa que el proceso principal no ha muerto. No significa que la aplicación esté respondiendo.
Aprendimos esto de la forma difícil cuando un backend de Python se quedó «congelado» pero el contenedor seguía activo. El balanceador de carga seguía enviándole tráfico a un servicio muerto.
La solución: Define healthchecks nativos en tu Compose.
services:
api:
image: mi-app:latest
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
Con esto, si la aplicación deja de responder, Docker lo sabrá y, dependiendo de tu configuración de orquestación (como un reverse proxy inteligente), dejará de enviarle tráfico.
2. Gestión de Imágenes: Multi-stage y Seguridad
Hace tres años, nuestras imágenes pesaban 1.5GB porque incluíamos todas las herramientas de desarrollo. Hoy, nuestras imágenes de producción rara vez superan los 150MB.
Lección: Usa Multi-stage builds en tu Dockerfile. Compila tu código en una imagen pesada con todas las dependencias, pero luego copia solo el binario o los archivos servibles a una imagen ligera (como Alpine Linux o Distroless).
Menos peso significa despliegues más rápidos y, lo más importante, una superficie de ataque mucho menor. Un hacker no puede usar herramientas de red o compiladores dentro de tu contenedor si esas herramientas no existen en la imagen final.
3. Redes: Aislamiento Químico de Servicios
Uno de los errores más comunes es exponer todos los contenedores al mundo exterior. Tu base de datos nunca debería tener puertos mapeados al host (ej. 3306:3306).
En nuestro diseño estándar, usamos dos redes:
1. frontend: Donde vive el reverse proxy (Nginx o Traefik) y las aplicaciones web.
2. backend: Donde viven las bases de datos, cachés (Redis) y servicios internos.
La aplicación web está conectada a ambas redes, pero la base de datos solo está en la red backend. Así, es físicamente imposible que alguien desde internet intente conectarse a la base de datos directamente, incluso si descubre la contraseña.
4. Persistencia y el Terror de los Volúmenes
«Los contenedores son efímeros». Si borras el contenedor, los datos se van. Parece obvio, pero en estos tres años he visto a más de un cliente perder datos por no entender bien los volúmenes.
Mi enfoque en GaboComputacion:
– Named Volumes para bases de datos: Usamos volúmenes nombrados de Docker (db_data:/var/lib/postgresql/data) porque Docker gestiona mejor los permisos y el rendimiento en estos casos.
– Bind Mounts para archivos de configuración: Usamos rutas locales (./config/nginx.conf:/etc/nginx/nginx.conf) para que sea fácil editar la configuración desde el host sin entrar al contenedor.
– Respaldos: No confíes en el volumen. Tenemos scripts de cron que ejecutan un docker exec para hacer un volcado de la base de datos (pg_dump, mysqldump) y lo suben a un bucket de Amazon S3 cada noche. La persistencia en Docker es local; el respaldo debe ser remoto.
5. El problema de las Variables de Entorno
Al principio, poníamos las contraseñas directamente en el archivo docker-compose.yml. Error de novato. Luego pasamos a archivos .env. Mejor, pero peligroso si los subes por error a Git.
Hoy, usamos una combinación de:
1. Archivos .env locales (nunca en el repositorio).
2. Variables de entorno del sistema inyectadas por nuestro pipeline de CI/CD (GitHub Actions).
3. Docker Secrets (si usamos modo Swarm) para lo más crítico.
Como IT Manager, mi regla es: «Si la contraseña está en texto plano en un archivo que un desarrollador puede ver en su laptop, tenemos un problema de seguridad».
6. Actualizaciones sin Caídas (Zero Downtime)
Docker Compose «puro» tiene un problema: cuando haces docker-compose up -d, detiene el contenedor viejo antes de levantar el nuevo. Esto genera unos segundos de caída.
Para solucionar esto sin pasar a Kubernetes, usamos un patrón de «Blue-Green Deployment» manual o usamos Traefik como reverse proxy. Traefik detecta cuando un nuevo contenedor se levanta, espera a que pase el healthcheck y luego cambia el tráfico suavemente.
También puedes usar el comando --scale:
docker-compose up -d --no-deps --scale app=2 --no-recreate app
Esto levanta una segunda instancia, y luego puedes eliminar la vieja. No es tan elegante como Kubernetes, pero para un blog o una tienda online, funciona de maravilla.
7. Monitoreo y Logs: No vueles a ciegas
Si no ves lo que pasa dentro, no estás en producción; estás jugando a la ruleta rusa.
Configuramos el driver de logs de Docker para limitar el tamaño de los archivos. Por defecto, Docker puede llenar tu disco duro con archivos de logs infinitos.
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
Además, centralizamos los logs. Usamos el stack Loki + Grafana. Los contenedores envían sus logs a Loki, y nosotros tenemos un tablero de Grafana donde puedo ver errores de 500 en tiempo real de todos mis clientes simultáneamente.
8. Herramientas que me hacen la vida fácil
- Portainer: No me canso de recomendarlo. Es una interfaz web para gestionar Docker. Me permite ver el estado de los stacks, revisar logs y abrir terminales en segundos sin tener que recordar IDs de contenedores largos.
- CTOP: Como el comando
topde Linux, pero para contenedores. Ideal para ver quién está consumiendo toda la CPU de forma rápida desde la terminal. - Watchtower: (Cuidado aquí). Es un contenedor que actualiza automáticamente tus otros contenedores cuando sale una imagen nueva. Lo usamos en desarrollo, pero en producción solo para servicios no críticos. En producción, las actualizaciones deben ser controladas por un humano o un pipeline de CI.
Conclusión: El punto dulce del IT Manager
Tras tres años de uso intensivo, mi conclusión es clara: Docker Compose es el «sweet spot» para la gran mayoría de las cargas de trabajo modernas. Te da el 80% de los beneficios de la contenedorización con el 20% de la complejidad de Kubernetes.
Como IT Manager, mi éxito se mide por la estabilidad del sistema y la felicidad del equipo. Docker Compose nos ha dado ambas. Nos ha permitido estandarizar entornos, eliminar el «en mi máquina funciona» y profesionalizar nuestros despliegues sin quemar a los ingenieros en el proceso.
Si estás pensando en dar el salto a contenedores en producción, no te sientas presionado por las tendencias que dictan las grandes corporaciones de Silicon Valley. Evalúa tus necesidades reales, tu tamaño de equipo y tu presupuesto. Lo más probable es que Docker Compose sea exactamente lo que necesitas para construir una infraestructura sólida, segura y, sobre todo, manejable.
La tecnología debe ser un puente, no un obstáculo. Y Docker Compose es uno de los puentes más resistentes que he construido en mis 20 años de carrera.
¿Qué te detiene para llevar tus contenedores a producción hoy mismo? Empieza pequeño, pero empieza bien.
Gabriel Guevara es experto en virtualización y gestión de infraestructuras, con un enfoque inquebrantable en la eficiencia operativa y la simplicidad técnica.
