En una frase
Docker Compose es una herramienta que te permite definir y ejecutar aplicaciones de múltiples contenedores usando un único archivo YAML legible por humanos, en lugar de un montón de comandos docker run largos y aparatosos.
El problema que resuelve
Al principio, existía docker run. Y estaba... más o menos bien. Para ejecutar un contenedor, está bien. Escribes tu comando, quizás con un mapeo de puertos y un volumen, y ¡boom!, tienes un servicio funcionando.
Pero ¿qué pasa cuando tu app no es un solo servicio? ¿Qué tal si tienes un frontend web, una API de backend, una base de datos PostgreSQL y una caché de Redis? De repente, no estás ejecutando un solo comando. Estás haciendo malabares con cuatro de ellos.
Tienes que crear manualmente una red para que puedan comunicarse entre sí. Tienes que asegurarte de no mapearlos accidentalmente todos al mismo puerto del host. Tienes que recordar el comando docker run exacto y larguísimo para cada servicio, con todas sus variables de entorno y montajes de volúmenes. Si quieres compartir esta configuración con un nuevo compañero de equipo, escribes un archivo README.md gigante que es básicamente una oración para que copie y pegue todo correctamente. Es la receta para el caos, los errores de tipeo y el clásico dilema de "¡bueno, en mi máquina funciona!".
Este es el caos que Docker Compose, nacido de un proyecto llamado Fig que Docker adquirió en 2014, fue diseñado para domar. Se trata de pasar de gestionar "mascotas" de contenedores individuales a orquestar un "rebaño" de aplicaciones completo. En lugar de emitir comandos imperativos (ejecuta esto, luego ejecuta aquello, únelos así...), escribes un único archivo declarativo (así es como se ve todo mi stack de aplicaciones) y dejas que Compose se encargue de los detalles engorrosos para hacerlo realidad.
Cómo funciona por debajo
En esencia, Docker Compose es un traductor. Traduce un archivo YAML descriptivo en una serie de llamadas a la API del Docker Engine. Es el "muéstralo, no lo cuentes" de la gestión de contenedores.
De los flags de docker run a las claves de YAML
Digamos que quieres ejecutar una base de datos PostgreSQL. El comando docker run podría parecer una sopa de letras de una película de hackers:
docker run --name my-postgres-db \
-e POSTGRES_PASSWORD=supersecret \
-p 5432:5432 \
-v pgdata:/var/lib/postgresql/data \
--restart unless-stopped \
-d postgres:14
Este comando tiene un nombre, una variable de entorno para la contraseña, un mapeo de puertos, un volumen con nombre para persistir datos, una política de reinicio y se ejecuta en segundo plano (-d).
Ahora, veamos el equivalente en Docker Compose en un archivo docker-compose.yml:
version: '3.8' # Especifica la versión del archivo Compose
services:
db: # Este es un nombre amigable para nuestro servicio
image: postgres:14
container_name: my-postgres-db
restart: unless-stopped
environment:
POSTGRES_PASSWORD: supersecret
ports:
- "5432:5432"
volumes:
- pgdata:/var/lib/postgresql/data
volumes:
pgdata: # Aquí declaramos el volumen con nombre
¿Ves el paralelismo? Los flags de la línea de comandos se han convertido en claves YAML ordenadas y organizadas. No es solo más bonito; es una definición de tu servicio que se puede versionar, compartir y es mucho menos propensa a errores.
Flag de docker run |
Clave de docker-compose.yml |
Qué hace |
|---|---|---|
postgres:14 (la imagen) |
image: postgres:14 |
Especifica la imagen de contenedor a usar. |
--name my-postgres-db |
container_name: my-postgres-db |
Le da un nombre específico al contenedor. |
-p 5432:5432 |
ports: ["5432:5432"] |
Mapea un puerto del host al contenedor. |
-v pgdata:/var/lib/... |
volumes: [pgdata:/var/lib/...] |
Monta un volumen para datos persistentes. |
-e VAR=value |
environment: { VAR: value } |
Establece variables de entorno dentro del contenedor. |
--restart unless-stopped |
restart: unless-stopped |
Define la política de reinicio del contenedor. |
La Magia de las Redes (Networking)
Aquí es donde Compose realmente brilla. Si agregaras un servicio de aplicación web a ese mismo archivo YAML, así:
services:
db:
# ... (nuestra configuración de postgres de arriba)
api:
image: my-cool-api:latest
ports:
- "8000:8000"
environment:
DATABASE_URL: postgresql://user:supersecret@db:5432/mydatabase
depends_on:
- db
Cuando ejecutas docker-compose up, Compose hace algo brillante: crea una red virtual dedicada para tu aplicación. Tanto el contenedor db como el api se conectan a esta red.
Ahora, el servicio api puede conectarse a la base de datos usando el hostname db — ¡el mismo nombre que le dimos al servicio en el archivo YAML! No más búsquedas de direcciones IP ni frágiles flags --link. Compose se encarga de toda la resolución de DNS dentro de su red privada. Así es como tu contenedor de la app web puede susurrarle cosas bonitas (o, ya sabes, consultas SQL) a tu contenedor de base de datos de forma segura y fiable.
El Ciclo de Vida de Compose
El comando docker-compose es tu panel de control para todo el stack de la aplicación.
docker-compose up: Este es el comando principal. Lee tudocker-compose.yml, comprueba el estado de tu sistema y hace lo que sea necesario para que coincida con el archivo. Creará redes, construirá imágenes (si especificaste una instrucciónbuild), creará volúmenes y arrancará todos los contenedores de tus servicios. Si lo ejecutas de nuevo, comprobará inteligentemente los cambios y solo recreará los contenedores que necesiten actualizarse.docker-compose down: El amigable opuesto deup. Detiene y elimina todos los contenedores y, por defecto, también elimina la red que creó. Es el botón de "limpia mi desastre". Para un borrón y cuenta nueva total,docker-compose down -vtambién elimina los volúmenes con nombre.docker-compose ps: Te muestra el estado de todos los contenedores gestionados por tu archivo Compose.docker-compose logs -f: Muestra en tiempo real los logs de todos tus servicios en un único flujo conveniente, codificado por colores según el servicio. Invaluable para depurar.
Historias del mundo real
La saga del "En mi máquina funciona"
Devon acaba de terminar una nueva funcionalidad para el panel de análisis de su empresa, que usa un backend en Node.js y una base de datos MongoDB. Funciona perfectamente en su laptop. Sube el código y le dice a un nuevo empleado, Sam, que lo baje y lo ejecute. El README de Devon tiene dos comandos docker run enormes. Sam, siendo humano, comete un error de tipeo en la ruta del volumen de la base de datos. La aplicación arranca, pero no puede guardar nada. Sam pasa tres horas depurando el código de la app, pensando que el error está ahí, mientras Devon insiste en que todo está bien. Finalmente, comparten pantalla y descubren el error de tipeo. La frustración es máxima.
La lección: Si hubieran usado un docker-compose.yml, el onboarding habría sido un único comando docker-compose up. Todo el entorno —puertos, volúmenes, variables de entorno— habría sido idéntico, eliminando toda una clase de errores de configuración y convirtiendo media jornada de depuración en un arranque de 30 segundos.
La pesadilla del pipeline de CI/CD
Un equipo de DevOps estaba construyendo un pipeline de integración continua para probar su aplicación de microservicios. La app tenía un servidor web, un API gateway, un servicio de usuarios y una caché de Redis. Su Jenkinsfile era un script horrible de comandos de shell, con cuatro llamadas docker run separadas y un montón de comandos sleep 30 con la esperanza de que la base de datos estuviera lista antes de que la API intentara conectarse a ella. Las pruebas eran inestables; a veces pasaban, a veces fallaban porque el sleep no era lo suficientemente largo.
La lección: Reescribieron el pipeline para usar Docker Compose. El Jenkinsfile se simplificó a docker-compose -f docker-compose.ci.yml up -d. Usaron depends_on con condition: service_healthy para asegurar que la API solo arrancara después de que la base de datos pasara su chequeo de salud interno. Las pruebas inestables desaparecieron de la noche a la mañana. Compose proporcionó una forma fiable y declarativa de definir y lanzar el entorno de pruebas exacto necesario, cada vez.
El sidecar olvidado
Una desarrolladora, María, desplegó su aplicación principal de Python en un contenedor Docker. Unas semanas después, su jefe le pidió que añadiera monitorización detallada del rendimiento. La mejor manera era ejecutar un exportador de Prometheus como un "sidecar" —un segundo contenedor que vive junto a la app principal, compartiendo su red para extraer métricas. María lo configuró con otro comando docker run. Pero ahora tenía que acordarse de iniciar, detener y reiniciar dos contenedores cada vez que hacía un cambio. Un día, reinició la app pero olvidó el sidecar, y perdieron un día entero de métricas.
La lección: María refactorizó su configuración a un docker-compose.yml. Definió su app de Python como un servicio y el exportador de Prometheus como un segundo. Ahora, docker-compose up les daba vida a ambos, y docker-compose down los retiraba juntos. Se gestionaban como una única unidad lógica, como debería ser. Compose hace que patrones como los sidecars sean triviales de gestionar.
Errores y trampas comunes
- Asumir que
depends_onespera a que el servicio esté listo. Por defecto,depends_onsolo espera a que el contenedor de la dependencia se inicie, no a que la aplicación dentro de él esté completamente inicializada y lista para aceptar conexiones. Tu API podría arrancar antes de que tu base de datos esté lista para ser consultada, causando un crash. La solución real es usar la instrucciónhealthchecken la definición de tu servicio de base de datos y luego usar la sintaxis larga dedepends_onconcondition: service_healthy. - Subir secretos a Git. Es increíblemente tentador simplemente escribir
POSTGRES_PASSWORD: mi-contraseña-secretadirectamente en eldocker-compose.yml. No lo hagas. Cualquiera con acceso a tu repositorio ahora tiene la contraseña de tu base de datos. La forma correcta es usar un archivo.env(que añades a.gitignore) y referenciar variables como${POSTGRES_PASSWORD}en tu archivo Compose. - Confundir montajes bind (bind mounts) con volúmenes con nombre (named volumes). Una ruta que empieza con
.o/(ej.,./mi-app:/app) es un montaje bind, que mapea un directorio de tu máquina anfitriona. Esto es genial para desarrollo, para que los cambios en el código se reflejen instantáneamente. Una ruta que empieza solo con un nombre (ej.,db-data:/var/lib/mysql) es un volumen con nombre gestionado por Docker. Esto es mejor para persistir datos como una base de datos, ya que es independiente de la plataforma y lo gestiona el motor de Docker. Usar el incorrecto puede llevar a problemas de permisos o a datos que no son tan portátiles como crees. - Usarlo para producción a gran escala. Docker Compose es fantástico para desarrollo, pruebas e incluso para pequeños despliegues de producción en un solo host. Pero NO es un reemplazo para un orquestador de contenedores completo como Kubernetes o Docker Swarm. No tiene un concepto integrado de clústeres de múltiples nodos, autorreparación, balanceo de carga entre nodos o actualizaciones progresivas sofisticadas. Conoce sus límites.
Por qué debería estar en tu radar
Deberías pensar en Docker Compose en el momento en que tu aplicación involucre más de una pieza móvil.
- ¿Estás construyendo una app web con una base de datos separada? Usa Compose.
- ¿Necesitas ejecutar tu app, una caché y un worker en segundo plano? Usa Compose.
- ¿Quieres dar a tus compañeros de equipo una forma de ejecutar todo el stack de desarrollo con un solo comando? Usa Compose.
- ¿Estás configurando un pipeline de CI que necesita levantar un entorno temporal para pruebas? Usa Compose.
Es la herramienta estándar de la industria por defecto para el desarrollo local basado en contenedores. Aprenderlo es el siguiente paso natural después de docker run, y es la puerta de entrada para entender el mundo más complejo de la orquestación de aplicaciones. Cierra la brecha entre ejecutar un solo contenedor y ejecutar un sistema distribuido completo.
Profundiza más
- Docker Docs: Compose file version 3 reference - La especificación oficial y fuente de la verdad para todas las claves y sintaxis disponibles.
- Docker Docs: Overview of Docker Compose - Una introducción de alto nivel de los propios creadores.
- YAML Spec - Dado que todos los archivos
docker-compose.ymlson YAML, entender la sintaxis (mapas, listas, anclas) puede ser muy útil. - Awesome Docker - Una lista curada de recursos de Docker, muchos de los cuales incluyen tutoriales y ejemplos de archivos
docker-compose.ymlpara varios stacks.