Docker Compose: Guía Completa con Ejemplos Prácticos

Docker Compose simplifica la orquestación de aplicaciones multi-contenedor al definir y ejecutar aplicaciones complejas usando un único archivo de configuración YAML. Esta guía completa cubre todo desde conceptos básicos hasta despliegues de producción avanzados con ejemplos del mundo real.

Tabla de Contenidos

Introducción a Docker Compose

Docker Compose es una herramienta para definir y ejecutar aplicaciones Docker multi-contenedor. En lugar de gestionar contenedores individualmente con extensos comandos docker run, Compose usa un archivo YAML para configurar todos los servicios, redes y volúmenes de la aplicación en un solo lugar.

¿Por Qué Usar Docker Compose?

  • Gestión Simplificada: Define el stack completo de la aplicación en un archivo
  • Reproducibilidad: Comparte configuraciones con tu equipo
  • Eficiencia de Desarrollo: Inicia el stack completo con un solo comando
  • Consistencia de Entorno: Misma configuración en dev, test y staging
  • Dependencias de Servicios: Define orden de inicio y dependencias
  • Escalado Fácil: Escala servicios hacia arriba o abajo con un comando

Docker Compose vs Kubernetes

  • Docker Compose: Desarrollo de host único y despliegues pequeños
  • Kubernetes: Orquestación de producción multi-host a escala

Requisitos Previos

Antes de comenzar, asegúrate de tener:

  • Docker Engine instalado (versión 20.10 o superior)
  • Docker Compose instalado (versión 2.0 o superior)
  • Conocimiento básico de Docker (imágenes, contenedores, volúmenes)
  • Editor de texto para archivos YAML
  • Comprensión de la arquitectura de tu aplicación

Verifica la instalación:

# Check Docker Compose version
docker compose version

# Or older standalone version
docker-compose --version

Notas de Instalación

Docker Compose v2 ahora está integrado como plugin de Docker CLI:

# New syntax (recommended)
docker compose up

# Old syntax (standalone)
docker-compose up

Esta guía usa la nueva sintaxis docker compose.

Conceptos Básicos de Docker Compose

Creando Tu Primer Archivo Compose

Crea un archivo llamado docker-compose.yml:

version: '3.8'

services:
  web:
    image: nginx:alpine
    ports:
      - "8080:80"

Inicia la aplicación:

docker compose up

Detén la aplicación:

docker compose down

Flujo de Trabajo Básico

# Start services
docker compose up -d

# View running services
docker compose ps

# View logs
docker compose logs

# Stop services
docker compose down

Estructura del Archivo Compose

Especificación de Versión

# Compose file format version
version: '3.8'

Nota: La versión 3.8 es ampliamente soportada. Versiones más nuevas (3.9, 3.10) agregan características.

Claves de Nivel Superior

version: '3.8'

services:      # Define containers
  service1:
  service2:

networks:      # Define networks
  network1:

volumes:       # Define volumes
  volume1:

configs:       # Define configs (Swarm mode)
  config1:

secrets:       # Define secrets (Swarm mode)
  secret1:

Ejemplo Mínimo

version: '3.8'

services:
  web:
    image: nginx:alpine
    ports:
      - "80:80"

Configuración de Servicios

Servicios Basados en Imagen

services:
  database:
    image: postgres:15-alpine
    environment:
      POSTGRES_PASSWORD: secret
    ports:
      - "5432:5432"

Servicios Basados en Construcción

services:
  app:
    build: .                    # Build from current directory
    ports:
      - "3000:3000"

  api:
    build:
      context: ./api           # Build context
      dockerfile: Dockerfile.prod  # Custom Dockerfile
      args:                    # Build arguments
        NODE_ENV: production

Nomenclatura de Contenedores

services:
  web:
    container_name: my-web-server
    image: nginx:alpine

Mapeo de Puertos

services:
  web:
    image: nginx
    ports:
      - "8080:80"              # host:container
      - "8443:443"
      - "127.0.0.1:9000:9000"  # bind to specific interface
      - "3000-3005:3000-3005"  # port range

Variables de Entorno

services:
  app:
    image: node:18-alpine
    environment:
      NODE_ENV: production
      API_KEY: ${API_KEY}      # From host environment
      DATABASE_URL: postgres://db:5432/mydb

Archivos de Entorno

services:
  app:
    image: node:18-alpine
    env_file:
      - .env
      - .env.production

Archivo .env:

NODE_ENV=production
API_KEY=your_api_key_here
DATABASE_URL=postgres://db:5432/mydb

Volúmenes

services:
  app:
    image: node:18-alpine
    volumes:
      - ./app:/usr/src/app           # bind mount
      - app-data:/app/data            # named volume
      - /var/run/docker.sock:/var/run/docker.sock:ro  # read-only

Depends On

services:
  web:
    image: nginx
    depends_on:
      - api
      - cache

  api:
    image: node:18-alpine
    depends_on:
      - database

  database:
    image: postgres:15-alpine

  cache:
    image: redis:alpine

Políticas de Reinicio

services:
  web:
    image: nginx
    restart: always            # always, no, on-failure, unless-stopped

Verificaciones de Salud

services:
  web:
    image: nginx
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s

Límites de Recursos

services:
  app:
    image: myapp
    deploy:
      resources:
        limits:
          cpus: '2'
          memory: 1G
        reservations:
          cpus: '0.5'
          memory: 512M

Command y Entrypoint

services:
  app:
    image: node:18-alpine
    command: npm start
    # Or with array syntax
    command: ["npm", "run", "dev"]

  worker:
    image: node:18-alpine
    entrypoint: /docker-entrypoint.sh

Directorio de Trabajo

services:
  app:
    image: node:18-alpine
    working_dir: /usr/src/app

Usuario

services:
  app:
    image: node:18-alpine
    user: "1000:1000"

Redes en Compose

Red Predeterminada

Docker Compose crea automáticamente una red predeterminada:

version: '3.8'

services:
  web:
    image: nginx

  api:
    image: node:18-alpine

Los servicios pueden comunicarse usando nombres de servicios como nombres de host.

Redes Personalizadas

version: '3.8'

services:
  web:
    image: nginx
    networks:
      - frontend

  api:
    image: node:18-alpine
    networks:
      - frontend
      - backend

  database:
    image: postgres
    networks:
      - backend

networks:
  frontend:
  backend:

Configuración de Red

networks:
  frontend:
    driver: bridge

  backend:
    driver: bridge
    ipam:
      config:
        - subnet: 172.28.0.0/16
          gateway: 172.28.0.1

Redes Externas

networks:
  existing-network:
    external: true
    name: my-pre-existing-network

Volúmenes en Compose

Volúmenes Nombrados

version: '3.8'

services:
  database:
    image: postgres:15-alpine
    volumes:
      - db-data:/var/lib/postgresql/data

volumes:
  db-data:

Configuración de Volumen

volumes:
  db-data:
    driver: local
    driver_opts:
      type: none
      device: /path/on/host
      o: bind

Volúmenes Externos

volumes:
  existing-volume:
    external: true
    name: my-pre-existing-volume

Variables de Entorno

Sustitución de Variables

services:
  web:
    image: nginx:${NGINX_VERSION:-latest}
    ports:
      - "${WEB_PORT:-8080}:80"

Archivo .env

Crea .env en el mismo directorio que docker-compose.yml:

NGINX_VERSION=alpine
WEB_PORT=8080
POSTGRES_PASSWORD=secretpassword

Múltiples Archivos de Entorno

services:
  app:
    image: myapp
    env_file:
      - ./common.env
      - ./prod.env

Comandos de Docker Compose

Iniciar Servicios

# Start all services
docker compose up

# Start in detached mode
docker compose up -d

# Start specific services
docker compose up web database

# Force recreate containers
docker compose up --force-recreate

# Build images before starting
docker compose up --build

Detener Servicios

# Stop all services
docker compose stop

# Stop specific service
docker compose stop web

# Stop and remove containers, networks
docker compose down

# Remove volumes too
docker compose down -v

# Remove images too
docker compose down --rmi all

Ver Estado

# List running services
docker compose ps

# Show all services (including stopped)
docker compose ps -a

# View service logs
docker compose logs

# Follow logs
docker compose logs -f

# Logs for specific service
docker compose logs -f web

# Show last 100 lines
docker compose logs --tail=100

Ejecutar Comandos

# Execute command in service
docker compose exec web sh

# Run one-off command
docker compose run web npm install

# Run without dependencies
docker compose run --no-deps web npm test

Construir Imágenes

# Build all services
docker compose build

# Build specific service
docker compose build web

# Build without cache
docker compose build --no-cache

# Build with parallel execution
docker compose build --parallel

Escalar Servicios

# Scale specific service
docker compose up -d --scale web=3

# Scale multiple services
docker compose up -d --scale web=3 --scale worker=5

Validación

# Validate compose file
docker compose config

# Render compose file with variables
docker compose config --resolve-image-digests

Otros Comandos Útiles

# Pause services
docker compose pause

# Unpause services
docker compose unpause

# Restart services
docker compose restart

# View resource usage
docker compose top

# Pull latest images
docker compose pull

Ejemplos del Mundo Real

Ejemplo 1: Stack WordPress

version: '3.8'

services:
  wordpress:
    image: wordpress:latest
    container_name: wordpress
    restart: unless-stopped
    ports:
      - "8080:80"
    environment:
      WORDPRESS_DB_HOST: database
      WORDPRESS_DB_USER: wordpress
      WORDPRESS_DB_PASSWORD: ${DB_PASSWORD}
      WORDPRESS_DB_NAME: wordpress
    volumes:
      - wordpress-data:/var/www/html
    depends_on:
      - database
    networks:
      - wp-network

  database:
    image: mysql:8.0
    container_name: wordpress-db
    restart: unless-stopped
    environment:
      MYSQL_DATABASE: wordpress
      MYSQL_USER: wordpress
      MYSQL_PASSWORD: ${DB_PASSWORD}
      MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
    volumes:
      - db-data:/var/lib/mysql
    networks:
      - wp-network

  phpmyadmin:
    image: phpmyadmin:latest
    container_name: phpmyadmin
    restart: unless-stopped
    ports:
      - "8081:80"
    environment:
      PMA_HOST: database
      MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
    depends_on:
      - database
    networks:
      - wp-network

volumes:
  wordpress-data:
  db-data:

networks:
  wp-network:
    driver: bridge

Ejemplo 2: Aplicación Full Stack Node.js

version: '3.8'

services:
  frontend:
    build:
      context: ./frontend
      dockerfile: Dockerfile
    container_name: react-frontend
    restart: unless-stopped
    ports:
      - "3000:3000"
    environment:
      - REACT_APP_API_URL=http://localhost:5000
    volumes:
      - ./frontend:/app
      - /app/node_modules
    networks:
      - app-network

  backend:
    build:
      context: ./backend
      dockerfile: Dockerfile
    container_name: node-backend
    restart: unless-stopped
    ports:
      - "5000:5000"
    environment:
      - NODE_ENV=production
      - DATABASE_URL=postgresql://postgres:${DB_PASSWORD}@database:5432/myapp
      - REDIS_URL=redis://redis:6379
    volumes:
      - ./backend:/app
      - /app/node_modules
    depends_on:
      - database
      - redis
    networks:
      - app-network

  database:
    image: postgres:15-alpine
    container_name: postgres-db
    restart: unless-stopped
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: ${DB_PASSWORD}
      POSTGRES_DB: myapp
    volumes:
      - postgres-data:/var/lib/postgresql/data
    networks:
      - app-network

  redis:
    image: redis:7-alpine
    container_name: redis-cache
    restart: unless-stopped
    ports:
      - "6379:6379"
    volumes:
      - redis-data:/data
    networks:
      - app-network

  nginx:
    image: nginx:alpine
    container_name: nginx-proxy
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
      - ./nginx/ssl:/etc/nginx/ssl:ro
    depends_on:
      - frontend
      - backend
    networks:
      - app-network

volumes:
  postgres-data:
  redis-data:

networks:
  app-network:
    driver: bridge

Ejemplo 3: Microservicios con Cola de Mensajes

version: '3.8'

services:
  api-gateway:
    build: ./api-gateway
    ports:
      - "8080:8080"
    environment:
      - USER_SERVICE_URL=http://user-service:3001
      - ORDER_SERVICE_URL=http://order-service:3002
    depends_on:
      - user-service
      - order-service
    networks:
      - microservices

  user-service:
    build: ./user-service
    environment:
      - DATABASE_URL=postgresql://postgres:${DB_PASSWORD}@postgres:5432/users
      - RABBITMQ_URL=amqp://rabbitmq:5672
    depends_on:
      - postgres
      - rabbitmq
    networks:
      - microservices

  order-service:
    build: ./order-service
    environment:
      - DATABASE_URL=postgresql://postgres:${DB_PASSWORD}@postgres:5432/orders
      - RABBITMQ_URL=amqp://rabbitmq:5672
    depends_on:
      - postgres
      - rabbitmq
    networks:
      - microservices

  postgres:
    image: postgres:15-alpine
    environment:
      POSTGRES_PASSWORD: ${DB_PASSWORD}
    volumes:
      - postgres-data:/var/lib/postgresql/data
      - ./init-db.sql:/docker-entrypoint-initdb.d/init.sql
    networks:
      - microservices

  rabbitmq:
    image: rabbitmq:3-management-alpine
    ports:
      - "5672:5672"
      - "15672:15672"
    environment:
      RABBITMQ_DEFAULT_USER: admin
      RABBITMQ_DEFAULT_PASS: ${RABBITMQ_PASSWORD}
    volumes:
      - rabbitmq-data:/var/lib/rabbitmq
    networks:
      - microservices

volumes:
  postgres-data:
  rabbitmq-data:

networks:
  microservices:
    driver: bridge

Ejemplo 4: Aplicación Python Django

version: '3.8'

services:
  web:
    build: .
    command: gunicorn myproject.wsgi:application --bind 0.0.0.0:8000
    volumes:
      - .:/code
      - static-volume:/code/staticfiles
      - media-volume:/code/media
    expose:
      - 8000
    environment:
      - DEBUG=0
      - SECRET_KEY=${SECRET_KEY}
      - DATABASE_URL=postgresql://postgres:${DB_PASSWORD}@db:5432/myapp
      - REDIS_URL=redis://redis:6379/1
    depends_on:
      - db
      - redis
    networks:
      - django-network

  nginx:
    image: nginx:alpine
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
      - static-volume:/code/staticfiles:ro
      - media-volume:/code/media:ro
    ports:
      - "80:80"
    depends_on:
      - web
    networks:
      - django-network

  db:
    image: postgres:15-alpine
    volumes:
      - postgres-data:/var/lib/postgresql/data
    environment:
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=${DB_PASSWORD}
      - POSTGRES_DB=myapp
    networks:
      - django-network

  redis:
    image: redis:7-alpine
    networks:
      - django-network

  celery:
    build: .
    command: celery -A myproject worker -l info
    volumes:
      - .:/code
    environment:
      - DATABASE_URL=postgresql://postgres:${DB_PASSWORD}@db:5432/myapp
      - REDIS_URL=redis://redis:6379/1
    depends_on:
      - db
      - redis
    networks:
      - django-network

  celery-beat:
    build: .
    command: celery -A myproject beat -l info
    volumes:
      - .:/code
    environment:
      - DATABASE_URL=postgresql://postgres:${DB_PASSWORD}@db:5432/myapp
      - REDIS_URL=redis://redis:6379/1
    depends_on:
      - db
      - redis
    networks:
      - django-network

volumes:
  postgres-data:
  static-volume:
  media-volume:

networks:
  django-network:
    driver: bridge

Ejemplo 5: Stack de Monitoreo (Prometheus + Grafana)

version: '3.8'

services:
  prometheus:
    image: prom/prometheus:latest
    container_name: prometheus
    restart: unless-stopped
    volumes:
      - ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml
      - prometheus-data:/prometheus
    command:
      - '--config.file=/etc/prometheus/prometheus.yml'
      - '--storage.tsdb.path=/prometheus'
    ports:
      - "9090:9090"
    networks:
      - monitoring

  grafana:
    image: grafana/grafana:latest
    container_name: grafana
    restart: unless-stopped
    environment:
      - GF_SECURITY_ADMIN_USER=admin
      - GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_PASSWORD}
      - GF_INSTALL_PLUGINS=grafana-piechart-panel
    volumes:
      - grafana-data:/var/lib/grafana
      - ./grafana/provisioning:/etc/grafana/provisioning
    ports:
      - "3000:3000"
    depends_on:
      - prometheus
    networks:
      - monitoring

  node-exporter:
    image: prom/node-exporter:latest
    container_name: node-exporter
    restart: unless-stopped
    volumes:
      - /proc:/host/proc:ro
      - /sys:/host/sys:ro
      - /:/rootfs:ro
    command:
      - '--path.procfs=/host/proc'
      - '--path.sysfs=/host/sys'
      - '--collector.filesystem.mount-points-exclude=^/(sys|proc|dev|host|etc)($$|/)'
    ports:
      - "9100:9100"
    networks:
      - monitoring

  cadvisor:
    image: gcr.io/cadvisor/cadvisor:latest
    container_name: cadvisor
    restart: unless-stopped
    volumes:
      - /:/rootfs:ro
      - /var/run:/var/run:ro
      - /sys:/sys:ro
      - /var/lib/docker/:/var/lib/docker:ro
    ports:
      - "8080:8080"
    networks:
      - monitoring

volumes:
  prometheus-data:
  grafana-data:

networks:
  monitoring:
    driver: bridge

Mejores Prácticas de Producción

Usar Control de Versiones

Siempre confirma docker-compose.yml en git:

git add docker-compose.yml .env.example
git commit -m "Add Docker Compose configuration"

Archivos Específicos de Entorno

# docker-compose.yml (base)
version: '3.8'
services:
  web:
    image: myapp

# docker-compose.override.yml (development)
version: '3.8'
services:
  web:
    volumes:
      - .:/app
    command: npm run dev

# docker-compose.prod.yml (production)
version: '3.8'
services:
  web:
    restart: unless-stopped
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"

Ejecuta con configuración específica:

# Development (uses override automatically)
docker compose up

# Production
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d

Mejores Prácticas de Seguridad

services:
  app:
    image: myapp
    # Run as non-root user
    user: "1000:1000"

    # Read-only root filesystem
    read_only: true
    tmpfs:
      - /tmp

    # Drop capabilities
    cap_drop:
      - ALL
    cap_add:
      - NET_BIND_SERVICE

    # Security options
    security_opt:
      - no-new-privileges:true

Límites de Recursos

services:
  app:
    image: myapp
    deploy:
      resources:
        limits:
          cpus: '2'
          memory: 2G
        reservations:
          cpus: '1'
          memory: 1G

Verificaciones de Salud

services:
  app:
    image: myapp
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
      interval: 30s
      timeout: 5s
      retries: 3
      start_period: 40s

Configuración de Logging

services:
  app:
    image: myapp
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"
        labels: "production"

Usar Secrets (Docker Swarm)

version: '3.8'

services:
  app:
    image: myapp
    secrets:
      - db_password
      - api_key

secrets:
  db_password:
    file: ./secrets/db_password.txt
  api_key:
    external: true

Solución de Problemas

Ver Logs de Servicio

# All services
docker compose logs

# Specific service
docker compose logs web

# Follow logs
docker compose logs -f

# Last 100 lines
docker compose logs --tail=100

El Contenedor No Inicia

# Check status
docker compose ps

# View logs
docker compose logs service-name

# Validate compose file
docker compose config

# Force recreate
docker compose up --force-recreate

Problemas de Red

# Inspect network
docker network ls
docker network inspect project_default

# Recreate network
docker compose down
docker compose up

Problemas de Permisos de Volumen

# Check volume
docker volume inspect project_volume-name

# Fix permissions in container
docker compose exec service-name chown -R user:group /path

Limpiar Recursos

# Stop and remove everything
docker compose down

# Remove volumes too
docker compose down -v

# Remove images
docker compose down --rmi all

# Complete cleanup
docker compose down -v --rmi all --remove-orphans

Depurar Servicio

# Execute shell in running container
docker compose exec service-name sh

# Run one-off command
docker compose run --rm service-name sh

# View container processes
docker compose top service-name

Conclusión

Docker Compose optimiza la gestión de aplicaciones multi-contenedor, haciéndolo esencial para flujos de trabajo de desarrollo modernos. Esta guía cubrió todo desde conceptos básicos hasta configuraciones listas para producción.

Conclusiones Clave

  • Configuración Única: Define el stack completo en docker-compose.yml
  • Dependencias de Servicios: Usa depends_on para orden de inicio
  • Aislamiento de Red: Creación automática de red con descubrimiento de servicios
  • Gestión de Volúmenes: Persiste datos con volúmenes nombrados y bind
  • Flexibilidad de Entorno: Usa archivos .env y sustitución de variables
  • Listo para Producción: Implementa verificaciones de salud, logging y límites de recursos

Referencia Rápida

# Essential Commands
docker compose up -d              # Start services
docker compose down               # Stop services
docker compose ps                 # List services
docker compose logs -f            # View logs
docker compose exec web sh        # Access shell
docker compose build              # Build images
docker compose pull               # Pull images
docker compose restart            # Restart services

# Management
docker compose config             # Validate file
docker compose up --build         # Rebuild and start
docker compose down -v            # Remove with volumes
docker compose scale web=3        # Scale service

Próximos Pasos

  1. Practica: Crea archivos compose para tus proyectos
  2. Optimiza: Implementa construcciones multi-etapa en Dockerfiles
  3. Asegura: Agrega verificaciones de salud y opciones de seguridad
  4. Monitorea: Integra soluciones de logging y monitoreo
  5. Orquesta: Avanza a Kubernetes para producción
  6. Automatiza: Integra con pipelines CI/CD
  7. Documenta: Mantén README completo con instrucciones de configuración

Docker Compose es perfecto para entornos de desarrollo y despliegues de host único. Para orquestación de producción multi-host, considera Kubernetes o Docker Swarm.