Guía Completa de Docker Compose: Orquestación de Aplicaciones Multi-Contenedor

Docker Compose orquestando múltiples contenedores

Docker Compose es una herramienta fundamental que lleva la gestión de contenedores al siguiente nivel. Mientras que Docker te permite ejecutar contenedores individuales, Docker Compose te permite definir y ejecutar aplicaciones multi-contenedor de manera elegante y declarativa.

En esta guía completa, aprenderás a dominar Docker Compose para orquestar aplicaciones complejas, gestionar servicios interconectados y simplificar tu flujo de trabajo de desarrollo y despliegue.

¿Qué es Docker Compose?

Docker Compose es una herramienta que utiliza archivos YAML para definir aplicaciones multi-contenedor. Con un solo comando, puedes crear, iniciar, detener y eliminar todos los servicios definidos en tu aplicación.

Ventajas de usar Docker Compose:

  • Simplicidad: Define toda tu aplicación en un archivo YAML
  • Reproducibilidad: Misma configuración en desarrollo, testing y producción
  • Escalabilidad: Escala servicios individuales según necesidades
  • Aislamiento: Cada aplicación tiene su propia red y volúmenes
  • Integración: Funciona perfectamente con herramientas de CI/CD

📦 Instalación de Docker Compose

Verificar si Docker Compose está Instalado

Docker Compose v2 viene incluido con Docker Desktop y las instalaciones modernas de Docker Engine:

# Verificar versión de Docker Compose
docker compose version

# Comando legacy (si usas versión antigua)
docker-compose --version

Instalación Manual (si es necesario)

Si no tienes Docker Compose instalado:

# Descargar la última versión
sudo curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose

# Hacer ejecutable
sudo chmod +x /usr/local/bin/docker-compose

# Verificar instalación
docker-compose --version

🚀 Conceptos Fundamentales

Estructura de un archivo docker-compose.yml

version: '3.8'  # Versión del formato de Compose

services:       # Definición de contenedores
  servicio1:
    # Configuración del primer servicio
  servicio2:
    # Configuración del segundo servicio

volumes:        # Volúmenes persistentes (opcional)
  datos:

networks:       # Redes personalizadas (opcional)
  mi-red:

Comandos Básicos de Docker Compose

# Iniciar aplicación (crear e iniciar contenedores)
docker compose up

# Iniciar en segundo plano
docker compose up -d

# Reconstruir imágenes antes de iniciar
docker compose up --build

# Detener aplicación
docker compose down

# Ver servicios en ejecución
docker compose ps

# Ver logs de todos los servicios
docker compose logs

# Ver logs de un servicio específico
docker compose logs nombre-servicio

📋 Ejemplo Práctico: Aplicación Web con Base de Datos

Vamos a crear una aplicación completa con frontend, backend y base de datos:

Estructura del Proyecto

mi-aplicacion/
├── docker-compose.yml
├── frontend/
│   ├── Dockerfile
│   └── index.html
├── backend/
│   ├── Dockerfile
│   ├── app.py
│   └── requirements.txt
└── database/
    └── init.sql

Frontend (Nginx + HTML)

frontend/Dockerfile:

FROM nginx:alpine
COPY index.html /usr/share/nginx/html/
EXPOSE 80

frontend/index.html:

<!DOCTYPE html>
<html>
<head>
    <title>Mi App con Docker Compose</title>
</head>
<body>
    <h1>Frontend de la Aplicación</h1>
    <p>Esta aplicación usa Docker Compose para orquestar múltiples servicios.</p>
    <button onclick="fetchData()">Obtener datos del Backend</button>
    <div id="resultado"></div>
    
    <script>
        async function fetchData() {
            try {
                const response = await fetch('/api/datos');
                const data = await response.json();
                document.getElementById('resultado').innerHTML = 
                    `<p>Respuesta del backend: ${JSON.stringify(data)}</p>`;
            } catch (error) {
                document.getElementById('resultado').innerHTML = 
                    `<p>Error: ${error.message}</p>`;
            }
        }
    </script>
</body>
</html>

Backend (Python Flask)

backend/Dockerfile:

FROM python:3.9-slim

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY app.py .

EXPOSE 5000

CMD ["python", "app.py"]

backend/requirements.txt:

Flask==2.3.2
psycopg2-binary==2.9.6

backend/app.py:

from flask import Flask, jsonify
import psycopg2
import os

app = Flask(__name__)

def get_db_connection():
    return psycopg2.connect(
        host=os.environ.get('DB_HOST', 'database'),
        database=os.environ.get('DB_NAME', 'miapp'),
        user=os.environ.get('DB_USER', 'usuario'),
        password=os.environ.get('DB_PASSWORD', 'password')
    )

@app.route('/api/datos')
def obtener_datos():
    try:
        conn = get_db_connection()
        cur = conn.cursor()
        cur.execute('SELECT * FROM usuarios LIMIT 5;')
        usuarios = cur.fetchall()
        cur.close()
        conn.close()
        
        return jsonify({
            'status': 'success',
            'usuarios': usuarios,
            'total': len(usuarios)
        })
    except Exception as e:
        return jsonify({'error': str(e)}), 500

@app.route('/health')
def health_check():
    return jsonify({'status': 'healthy'})

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000, debug=True)

Base de Datos (PostgreSQL)

database/init.sql:

-- Crear tabla de usuarios
CREATE TABLE IF NOT EXISTS usuarios (
    id SERIAL PRIMARY KEY,
    nombre VARCHAR(100) NOT NULL,
    email VARCHAR(100) UNIQUE NOT NULL,
    fecha_creacion TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- Insertar datos de ejemplo
INSERT INTO usuarios (nombre, email) VALUES 
    ('Juan Pérez', 'juan@ejemplo.com'),
    ('María García', 'maria@ejemplo.com'),
    ('Carlos López', 'carlos@ejemplo.com'),
    ('Ana Martín', 'ana@ejemplo.com'),
    ('Luis Rodríguez', 'luis@ejemplo.com');

Docker Compose Configuration

docker-compose.yml:

version: '3.8'

services:
  # Servicio de Frontend
  frontend:
    build: ./frontend
    ports:
      - "8080:80"
    depends_on:
      - backend
    networks:
      - app-network
    restart: unless-stopped

  # Servicio de Backend
  backend:
    build: ./backend
    ports:
      - "5000:5000"
    environment:
      - DB_HOST=database
      - DB_NAME=miapp
      - DB_USER=usuario
      - DB_PASSWORD=password123
    depends_on:
      database:
        condition: service_healthy
    networks:
      - app-network
    restart: unless-stopped
    volumes:
      - ./backend:/app  # Para desarrollo hot-reload

  # Servicio de Base de Datos
  database:
    image: postgres:15-alpine
    environment:
      - POSTGRES_DB=miapp
      - POSTGRES_USER=usuario
      - POSTGRES_PASSWORD=password123
    volumes:
      - postgres_data:/var/lib/postgresql/data
      - ./database/init.sql:/docker-entrypoint-initdb.d/init.sql
    networks:
      - app-network
    restart: unless-stopped
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U usuario -d miapp"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 60s

  # Servicio de Proxy Reverso (Nginx)
  proxy:
    image: nginx:alpine
    ports:
      - "80:80"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf
    depends_on:
      - frontend
      - backend
    networks:
      - app-network
    restart: unless-stopped

# Definición de volúmenes
volumes:
  postgres_data:
    driver: local

# Definición de redes
networks:
  app-network:
    driver: bridge

Configuración de Nginx (Proxy Reverso)

nginx.conf:

events {
    worker_connections 1024;
}

http {
    upstream backend {
        server backend:5000;
    }

    server {
        listen 80;
        
        # Servir frontend
        location / {
            proxy_pass http://frontend;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
        }
        
        # Proxy para API backend
        location /api/ {
            proxy_pass http://backend;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        }
    }
}

🎛️ Comandos Avanzados de Docker Compose

Gestión de Servicios

# Escalar un servicio específico
docker compose up -d --scale backend=3

# Reconstruir un servicio específico
docker compose build backend

# Reiniciar un servicio
docker compose restart backend

# Detener un servicio específico
docker compose stop backend

# Eliminar servicios y volúmenes
docker compose down -v

Monitoreo y Debugging

# Ver logs en tiempo real
docker compose logs -f

# Ver logs de un servicio específico
docker compose logs -f backend

# Ejecutar comandos en un servicio
docker compose exec backend bash

# Ver estadísticas de recursos
docker compose top

# Inspeccionar configuración
docker compose config

Entornos de Desarrollo vs Producción

docker-compose.override.yml (para desarrollo):

version: '3.8'

services:
  backend:
    volumes:
      - ./backend:/app
    environment:
      - FLASK_ENV=development
      - FLASK_DEBUG=1
    command: python app.py

  database:
    ports:
      - "5432:5432"  # Exponer DB para herramientas externas

docker-compose.prod.yml (para producción):

version: '3.8'

services:
  backend:
    restart: always
    environment:
      - FLASK_ENV=production
    deploy:
      resources:
        limits:
          memory: 512M
        reservations:
          memory: 256M

  database:
    deploy:
      resources:
        limits:
          memory: 1G
        reservations:
          memory: 512M
# Usar configuración de producción
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d

🔒 Mejores Prácticas de Seguridad

Variables de Entorno Seguras

.env:

# Base de datos
DB_PASSWORD=mi_password_super_seguro
DB_USER=usuario_app
DB_NAME=produccion_db

# Configuración de aplicación
SECRET_KEY=clave_secreta_muy_larga_y_compleja
API_TOKEN=token_api_seguro

docker-compose.yml con variables:

services:
  database:
    environment:
      - POSTGRES_PASSWORD=${DB_PASSWORD}
      - POSTGRES_USER=${DB_USER}
      - POSTGRES_DB=${DB_NAME}
    # No exponer puertos en producción
    # ports:
    #   - "5432:5432"

Health Checks Avanzados

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

🚨 Troubleshooting Común

Problemas de Red

# Verificar redes creadas
docker network ls

# Inspeccionar red de la aplicación
docker network inspect mi-aplicacion_app-network

# Probar conectividad entre servicios
docker compose exec backend ping database

Problemas de Volúmenes

# Listar volúmenes
docker volume ls

# Inspeccionar volumen
docker volume inspect mi-aplicacion_postgres_data

# Backup de volumen
docker run --rm -v mi-aplicacion_postgres_data:/data -v $(pwd):/backup ubuntu tar czf /backup/backup.tar.gz /data

Debugging de Servicios

# Ver configuración final de compose
docker compose config

# Ver eventos en tiempo real
docker compose events

# Verificar estado de servicios
docker compose ps --services

📊 Monitoreo con Docker Compose

Stack de Monitoreo Completo

docker-compose.monitoring.yml:

version: '3.8'

services:
  prometheus:
    image: prom/prometheus:latest
    ports:
      - "9090:9090"
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
      - prometheus_data:/prometheus
    command:
      - '--config.file=/etc/prometheus/prometheus.yml'
      - '--storage.tsdb.path=/prometheus'
      - '--web.console.libraries=/etc/prometheus/console_libraries'
      - '--web.console.templates=/etc/prometheus/consoles'

  grafana:
    image: grafana/grafana:latest
    ports:
      - "3000:3000"
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=admin123
    volumes:
      - grafana_data:/var/lib/grafana

  node-exporter:
    image: prom/node-exporter:latest
    ports:
      - "9100:9100"
    volumes:
      - /proc:/host/proc:ro
      - /sys:/host/sys:ro
      - /:/rootfs:ro
    command:
      - '--path.procfs=/host/proc'
      - '--path.sysfs=/host/sys'
      - '--collector.filesystem.ignored-mount-points'
      - '^/(sys|proc|dev|host|etc|rootfs/var/lib/docker/containers|rootfs/var/lib/docker/overlay2|rootfs/run/docker/netns|rootfs/var/lib/docker/aufs)($$|/)'

volumes:
  prometheus_data:
  grafana_data:

🎯 Mejores Prácticas Avanzadas

  1. Usa health checks para todos los servicios críticos
  2. Implementa restart policies apropiadas para cada servicio
  3. Separa configuraciones de desarrollo y producción
  4. Usa .dockerignore para optimizar contextos de build
  5. Implementa logging centralizado con drivers de log
  6. Usa networks personalizadas para segmentación
  7. Configura resource limits para prevenir resource starvation
  8. Implementa backup strategies para volúmenes de datos
  9. Usa secrets management para información sensible
  10. Monitora métricas y logs de aplicaciones

Conclusión

Docker Compose transforma la complejidad de gestionar aplicaciones multi-contenedor en una experiencia simple y declarativa. Con esta guía, tienes todo lo necesario para crear, desplegar y mantener aplicaciones robustas y escalables.

Beneficios clave que has aprendido:

  • Orquestación simplificada de servicios múltiples
  • Configuración declarativa y versionable
  • Separación clara entre entornos
  • Escalabilidad horizontal sencilla
  • Integración natural con flujos DevOps

Próximos pasos recomendados:

  • Explora Docker Swarm para clustering
  • Aprende Kubernetes para orquestación enterprise
  • Implementa CI/CD con Docker Compose
  • Estudia patrones de microservicios avanzados

¡Con Docker Compose, gestionar aplicaciones complejas nunca fue tan sencillo! 🐳✨

In conclusion, achieving cross-browser compatibility is an ongoing commitment that requires careful planning, testing, and adaptation. By prioritizing these strategies, you can ensure that your website provides a consistent and enjoyable experience for users, regardless of the browser they choose to use. Embracing diversity in the digital realm ultimately leads to a broader audience and increased user satisfaction.