Antes de Docker Compose, levantar un entorno de desarrollo con varios servicios era un ritual de paciencia. Un docker run para la base de datos, otro para el backend, otro para Redis, acordarse de los flags de red, los volúmenes, las variables de entorno… y rezar para que el próximo dev del equipo pudiera repetir exactamente los mismos pasos.
Docker Compose resolvió todo eso con un solo archivo YAML y un comando. En esta primera parte de la serie te cuento qué es, de dónde viene y cómo se estructura ese archivo que se vuelve la única fuente de verdad de tu entorno.
¿Qué es Docker Compose y por qué importa?
Docker Compose es una herramienta diseñada para definir y ejecutar aplicaciones que constan de múltiples contenedores. Su propósito es simple pero poderoso: en lugar de gestionar cada contenedor con comandos individuales, describís el estado deseado de todo el sistema en un archivo compose.yaml, y Compose se encarga de alcanzar ese estado.
Los beneficios que más uso en el día a día:
- Entornos reproducibles: el clásico «funciona en mi máquina» desaparece. Si el entorno está en código, todos ejecutan lo mismo.
- Onboarding en minutos: un nuevo dev solo necesita Docker instalado. Un
docker compose upy tiene todo funcionando. - IaC a nivel desarrollador: el
compose.yamlvive junto al código en Git. El entorno es versionable, revisable y auditable. - Ciclos rápidos: Compose reutiliza contenedores que no cambiaron. Los reinicios son rápidos.
De Fig a Compose V2: una historia corta pero importante
Docker Compose no nació en Docker. Empezó como Fig, un proyecto de la empresa Orchardup que ya ofrecía exactamente esta idea: definir y levantar entornos multi-contenedor con un archivo YAML. Docker Inc. vio el valor, adquirió Orchardup en 2013 y relanzó Fig como Docker Compose.
Hoy existen dos versiones del CLI que vale la pena distinguir:
| Característica | V1 (legado) | V2 (actual) |
|---|---|---|
| Comando | docker-compose (con guion) | docker compose (sin guion) |
| Lenguaje | Python | Go |
Campo version: | Requerido (2.0 a 3.8) | Ignorado (usa Compose Spec) |
| Integración | Binario separado | Plugin del Docker CLI |
Si encontrás documentación con docker-compose (con guion) y version: '3.8' en la primera línea, es sintaxis de V1. Todo el contenido de esta serie usa V2, que es el estándar actual.
El archivo compose.yaml: la única fuente de verdad
El corazón de Docker Compose es su archivo de configuración. Puede llamarse compose.yaml (preferido), compose.yml, docker-compose.yaml o docker-compose.yml — todos son reconocidos por compatibilidad.
Las directivas de nivel superior que vas a usar en prácticamente todo proyecto:
# compose.yaml
services: # Los contenedores de tu aplicación (obligatorio)
networks: # Redes personalizadas entre servicios (opcional)
volumes: # Volúmenes nombrados para persistencia (opcional)
secrets: # Datos sensibles (contraseñas, claves API) (opcional)
configs: # Configuración no sensible externa a la imagen (opcional)
Definiendo servicios
Cada servicio representa un componente de tu aplicación corriendo en uno o más contenedores. Los atributos que más uso:
services:
web:
image: nginx:alpine # Imagen de Docker Hub
container_name: mi_nginx # Nombre personalizado (cuidado si escalás)
ports:
- "8080:80" # host:contenedor
environment:
APP_ENV: production
env_file:
- .env # Variables desde archivo (no versionarlo con secretos)
restart: unless-stopped # Reinicio automático excepto si lo detenés manualmente
api:
build: # Construir desde Dockerfile en lugar de usar imagen
context: .
dockerfile: Dockerfile
args:
- NODE_VERSION=20 # ARGs del Dockerfile
target: production # Para builds multi-stage
command: ["npm", "start"] # Sobrescribe CMD del Dockerfile
depends_on:
- db # Arranca después de 'db'
db:
image: postgres:15-alpine
volumes:
- pg_data:/var/lib/postgresql/data # Volumen nombrado para persistencia
La flexibilidad de elegir entre image y build es clave: usás imagen oficial para componentes estándar (Postgres, Redis, Nginx) y build para tu código propio. Pueden convivir sin problema en el mismo archivo.
Redes: cómo se comunican los servicios
Cuando levantás un proyecto con docker compose up, Compose crea automáticamente una red bridge y conecta todos los servicios a ella. La magia: los servicios se encuentran entre sí usando su nombre como hostname DNS.
services:
api:
image: mi-api
environment:
# 'db' es el nombre del servicio, funciona como hostname
DB_HOST: db
REDIS_HOST: cache
db:
image: postgres:15
cache:
image: redis:alpine
Para mayor control, podés definir redes personalizadas y segmentar qué servicios pueden verse entre sí:
services:
frontend:
networks: [public] # Solo en la red pública
api:
networks: [public, internal] # En ambas redes
db:
networks: [internal] # Solo en la red interna, no expuesta al exterior
networks:
public:
driver: bridge
internal:
driver: bridge
internal: true # Sin acceso al exterior
Volúmenes: persistencia de datos
Por defecto, los datos dentro de un contenedor desaparecen cuando el contenedor se elimina. Los volúmenes resuelven esto. Hay dos tipos que uso constantemente:
services:
db:
image: postgres:15
volumes:
# Tipo 1: Volumen nombrado — Docker gestiona el almacenamiento
# Ideal para producción y datos de base de datos
- pg_data:/var/lib/postgresql/data
api:
build: .
volumes:
# Tipo 2: Bind mount — mapea un directorio del host al contenedor
# Ideal para desarrollo: cambios en el código se reflejan al instante
- .:/usr/src/app
# Los volúmenes nombrados se declaran en el nivel superior
volumes:
pg_data:
driver: local
La regla que sigo: volúmenes nombrados para datos que deben persistir (bases de datos, uploads, logs), bind mounts para el código fuente durante el desarrollo para tener hot-reload sin reconstruir la imagen.
Resumen de la Parte 1
- Docker Compose define entornos multi-contenedor en un solo archivo declarativo
- Usá siempre V2 (
docker composesin guion); el campoversion:ya no es necesario - Los servicios se comunican usando su nombre como hostname — no necesitás IPs ni configuración manual
- Volúmenes nombrados para persistencia, bind mounts para desarrollo ágil
En la Parte 2 vemos los comandos esenciales del CLI y las características que hacen que los entornos sean robustos: depends_on con healthchecks, escalado y perfiles.
Deja una respuesta