Introducción a Docker Compose y Archivo YAML

Lectura
20 min~7 min lectura
CONCEPTO CLAVE: Docker Compose es una herramienta que permite definir y ejecutar aplicaciones Docker multi-contenedor. Con un único archivo YAML, puedes configurar todos los servicios de tu aplicación, redes y volúmenes, luego iniciarlos con un solo comando.

¿Qué es Docker Compose?

Cuando trabajas en proyectos reales de desarrollo, rara vez tendrás un solo contenedor ejecutándose de forma aislada. La mayoría de aplicaciones modernas requieren múltiples servicios funcionando simultáneamente: una base de datos, un servidor web, un cache, una cola de mensajes, entre otros. Docker Compose resuelve el problema de orquestar estos múltiples contenedores de manera declarativa y reproducible.

Antes de Docker Compose, iniciar un entorno de desarrollo completo significaba ejecutar manualmente múltiples comandos docker run, recordar el orden de ejecución, configurar las redes, linkear los contenedores, y gestionar los volúmenes. Este proceso era propenso a errores y difícil de documentar o compartir con el equipo.

📌 Nota importante: Docker Compose no es solo para desarrollo. También se utiliza en entornos de staging, CI/CD y incluso producción en arquitecturas más simples.

El Archivo docker-compose.yml

El corazón de Docker Compose es el archivo docker-compose.yml. Este archivo YAML define todos los servicios, redes y volúmenes necesarios para tu aplicación. Veamos su estructura básica:

version: '3.8'

services:
  web:
    image: nginx:alpine
    ports:
      - "80:80"
    depends_on:
      - api
    networks:
      - frontend
  
  api:
    build: ./api
    environment:
      - NODE_ENV=development
      - DATABASE_URL=postgres://db:5432/app
    depends_on:
      - db
    networks:
      - frontend
      - backend
  
  db:
    image: postgres:14
    volumes:
      - postgres_data:/var/lib/postgresql/data
    environment:
      - POSTGRES_DB=app
      - POSTGRES_USER=user
      - POSTGRES_PASSWORD=secret
    networks:
      - backend

networks:
  frontend:
  backend:

volumes:
  postgres_data:

Estructura del Archivo YAML

Versión

El campo version especifica la versión del formato de Docker Compose. Aunque es opcional en versiones recientes, es una buena práctica incluirla para garantizar compatibilidad y comportamiento predecible.

Versión Características Recomendación
3.x Estándar para producción, sin dependencias de Swarm ✅ Recomendada para la mayoría de casos
2.x Compatible con versiones anteriores ⚠️ Solo para legacy systems
3.8+ Secrets, configs, y características avanzadas ✅ Para proyectos que lo necesiten

Services

La sección services define cada contenedor que conforma tu aplicación. Cada servicio puede especificar:

  • image: La imagen Docker a utilizar
  • build: Ruta al Dockerfile para construir la imagen
  • ports: Mapeo de puertos (host:contenedor)
  • environment: Variables de entorno
  • volumes: Montaje de volúmenes
  • depends_on: Dependencias entre servicios
  • networks: Redes a las que conectar
💡 Tip práctico: Utiliza build cuando necesitas personalizar la imagen con tu código, y image cuando usas imágenes pre-construidas de Docker Hub.

Comandos Esenciales de Docker Compose

  1. docker-compose up: Crea y arranca todos los servicios definidos. Usa -d para ejecutarlos en segundo plano.
  2. docker-compose down: Detiene y elimina todos los contenedores, redes y volúmenes creados por up. Usa -v para eliminar también los volúmenes anónimos.
  3. docker-compose build: Construye o reconstruye las imágenes definidas con build.
  4. docker-compose logs: Muestra los logs de todos los servicios. Usa -f para seguimiento en tiempo real.
  5. docker-compose ps: Lista todos los contenedores en ejecución.
  6. docker-compose exec: Ejecuta un comando dentro de un servicio en ejecución.
  7. docker-compose restart: Reinicia todos los servicios o los especificados.

Ejemplo Práctico: Stack MEAN

Veamos un ejemplo completo de una aplicación MEAN (MongoDB, Express, Angular, Node.js):

version: '3.8'

services:
  mongodb:
    image: mongo:5.0
    restart: unless-stopped
    environment:
      MONGO_INITDB_ROOT_USERNAME: admin
      MONGO_INITDB_ROOT_PASSWORD: password123
    volumes:
      - mongodb_data:/data/db
    ports:
      - "27017:27017"
    networks:
      - backend

  backend:
    build:
      context: ./server
      dockerfile: Dockerfile
    working_dir: /app
    command: npm start
    environment:
      NODE_ENV: development
      MONGODB_URI: mongodb://admin:password123@mongodb:27017/?authSource=admin
      PORT: 3000
    volumes:
      - ./server:/app
      - /app/node_modules
    ports:
      - "3000:3000"
    depends_on:
      - mongodb
    networks:
      - backend

  frontend:
    build:
      context: ./client
      dockerfile: Dockerfile
    stdin_open: true
    tty: true
    ports:
      - "4200:4200"
    depends_on:
      - backend
    networks:
      - frontend

  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
    depends_on:
      - backend
      - frontend
    networks:
      - frontend
      - backend

networks:
  frontend:
    driver: bridge
  backend:
    driver: bridge

volumes:
  mongodb_data:
⚠️ Advertencia: En el ejemplo anterior, el volumen /app/node_modules en el servicio backend es un volumen anónimo. Esto es crucial para evitar que tu directorio local sobrescriba node_modules dentro del contenedor durante el desarrollo.

Variables de Entorno y Archivos .env

Docker Compose soporta archivos .env para gestionar variables de entorno. Este archivo debe estar en el mismo directorio que tu docker-compose.yml:

# .env
NODE_ENV=development
MONGODB_URI=mongodb://admin:password123@mongodb:27017/?authSource=admin
API_PORT=3000
CLIENT_PORT=4200

En tu docker-compose.yml, puedes referenciar estas variables:

services:
  backend:
    environment:
      - NODE_ENV=${NODE_ENV}
      - MONGODB_URI=${MONGODB_URI}
      - PORT=${API_PORT}
💡 Tip de seguridad: Nunca commits tu archivo .env al repositorio. Agrégalo a tu .gitignore y proporciona un archivo .env.example con valores de referencia para que otros desarrolladores sepan qué variables necesitan configurar.

Gestión de Redes

Docker Compose crea automáticamente una red por defecto para todos los servicios. Sin embargo, para arquitecturas más complejas, puedes definir redes personalizadas:

networks:
  frontend:
    driver: bridge
  backend:
    driver: bridge
  monitoring:
    driver: bridge

Los servicios se conectan a las redes especificadas en su propiedad networks. Esto permite:

  • Aislamiento de servicios por funcionalidad
  • Control de qué servicios pueden comunicarse entre sí
  • Segmentación para políticas de seguridad

Override y Extensión de Archivos

Docker Compose permite crear archivos docker-compose.override.yml que automáticamente se aplican sobre el archivo principal. Esto es útil para:

  • Configuraciones específicas de desarrollo vs producción
  • Personalización local sin modificar el archivo principal
  • Separación de configuraciones sensibles
# docker-compose.override.yml (desarrollo local)
services:
  backend:
    environment:
      - DEBUG=true
    volumes:
      - ./server:/app
  frontend:
    build:
      target: development
📌 Comando útil: Usa docker-compose -f docker-compose.yml -f docker-compose.prod.yml config > docker-compose.full.yml para fusionar y verificar la configuración final.
Ver más: Características Avanzadas

Healthchecks

Define verificaciones de salud para tus servicios:

services:
  db:
    image: postgres:14
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 10s
      timeout: 5s
      retries: 5

Recursos y Límites

services:
  api:
    deploy:
      resources:
        limits:
          cpus: '0.5'
          memory: 512M
        reservations:
          cpus: '0.25'
          memory: 256M

Escalado

docker-compose up --scale api=3

Dependencias y Orden de Inicio

El campo depends_on asegura que los servicios inicien en el orden correcto. Sin embargo, es importante entender sus limitaciones:

«depends_on solo garantiza el orden de inicio, no que el servicio dependiente esté listo para aceptar conexiones.»

Para esperar a que un servicio esté realmente disponible, puedes usar:

  1. Scripts de espera: Espera a que el puerto esté abierto antes de conectar.
  2. Healthchecks: Define condiciones de salud y espera a que el servicio sea healthy.
  3. wait-for-it.sh o dockerize: Herramientas específicas para espera de servicios.
services:
  api:
    depends_on:
      db:
        condition: service_healthy
  db:
    image: postgres:14
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      timeout: 3s
      retries: 5
🧠 Quiz

¿Cuál es la principal ventaja de usar un archivo docker-compose.override.yml?

  • A) Aumentar el rendimiento de los contenedores
  • B) Mantener configuraciones específicas de entorno sin modificar el archivo principal
  • C) Eliminar la necesidad de archivos .env
  • D) Reemplazar completamente docker-compose.yml
✅ Respuesta correcta: B) Permite agregar o sobrescribir configuraciones para entornos específicos (desarrollo, producción, staging) de forma limpia y modular.

Flujo de Trabajo Típico en Desarrollo

Un flujo de trabajo típico usando Docker Compose sería:

  1. Clonar el repositorio: git clone <repo-url>
  2. Configurar entorno: Copiar .env.example a .env y ajustar valores
  3. Iniciar servicios: docker-compose up -d --build
  4. Verificar estado: docker-compose ps
  5. Seguir logs: docker-compose logs -f
  6. Desarrollar: Editar código, ver cambios reflejados automáticamente
  7. Detener entorno: docker-compose down (o docker-compose down -v para limpiar volúmenes)
💡 Tip de productividad: Crea alias en tu shell para los comandos más usados: alias dc="docker-compose" y alias dcup="docker-compose up -d --build".

Debugging con Docker Compose

Cuando algo no funciona como esperas, Docker Compose ofrece herramientas útiles:

  • docker-compose config: Valida y muestra la configuración fusionada
  • docker-compose logs [service]: Revisa los logs de un servicio específico
  • docker-compose exec [service] sh: Accede al shell del contenedor para inspeccionar
  • docker-compose events: Observa eventos en tiempo real
⚠️ Problema común: Si tu aplicación no se conecta a la base de datos, verifica que estés usando el nombre del servicio como hostname (ej: mongodb://localhost:27017mongodb://mongodb:27017).

Conclusión

Docker Compose transforma la gestión de aplicaciones multi-contenedor de un proceso manual y propenso a errores en una experiencia declarativa, reproducible y documentada. Dominar su uso es fundamental para cualquier desarrollador que trabaje con contenedores Docker, ya sea en desarrollo local, pruebas automatizadas o despliegues de integración continua.

CONCEPTO CLAVE: El poder de Docker Compose radica en su simplicidad: un archivo YAML define todo tu entorno. Esto significa que cualquier miembro del equipo puede replicar exactamente el mismo comportamiento con un solo comando docker-compose up.