Cambiar tema

Orquestación multi-servicio con Docker Compose: entorno local en un solo comando

Easton editorial illustration: route-map drafting table

La tarde de mi primer día en el equipo, llevaba ya el quinto error en pantalla: puerto MySQL ocupado, Redis incompatible, RabbitMQ que no conectaba. Un compañero miró mi monitor y suspiró: “¿Cuánto llevas instalando?”

“Desde las nueve de la mañana.”

Cuatro horas para instalar tres bases de datos. Y aún faltaban ElasticSearch y MongoDB.

Ahí entendí que el entorno local puede ser un pozo sin fondo.

Luego el equipo adoptó Docker Compose. Un nuevo miembro clona el repo, docker-compose up -d, y en cinco minutos MySQL, Redis, RabbitMQ, API y Web están arriba. ¿Cambiar de proyecto? Otro directorio, otro compose. ¿Limpiar? docker-compose down -v y no queda rastro.

Este artículo cuenta ese cambio: cómo orquestar varios servicios con Docker Compose y pasar del infierno de configuración manual a un solo comando.

Por qué necesitas orquestación multi-servicio

Hace diez años, con apps monolíticas, bastaba instalar el JDK y apuntar a una base de datos. Hoy la mayoría de proyectos son microservicios o al menos frontend/backend separados.

Un entorno local típico incluye: frontend, API, MySQL, Redis, RabbitMQ… y a veces ElasticSearch o MongoDB.

El dolor de instalar a mano

Cada máquina repite el ritual. ¿MySQL 5.7 u 8.0? Versión incorrecta, SQL incompatible. Redis en 6379, ¿y si está ocupado? RabbitMQ exige Erlang… Solo RabbitMQ me costó media hora de documentación.

Después vienen conflictos de versión y puertos. Cambias de proyecto A a B: ambos quieren 3306. Reconfiguras, paras servicios, vuelves a A y otra vez.

La pesadilla del equipo

“En mi máquina funciona.”

El nuevo clona, instala, no arranca. MySQL 8.0 vs 5.7 del proyecto, Redis con o sin contraseña… Medio día perdido para poner en marcha el entorno.

La idea de Compose

Empaqueta todo en contenedores y unifica la configuración en un archivo. No te importa cómo se instala cada base de datos: defines servicios, puertos y variables, y arrancas. Cambiar de proyecto = otro compose. Limpiar = un comando.

Es pasar de montar un PC pieza a pieza a comprar uno listo para usar.

Configuración central de docker-compose.yml

Ejemplo con cuatro servicios: Web, API, MySQL y Redis.

# docker-compose.yml
version: "3.8"  # Versión del archivo Compose; 3.8 cubre la mayoría de opciones

services:
  # Frontend
  web:
    build: ./frontend  # Construye imagen desde ./frontend
    ports:
      - "3000:3000"  # host:contenedor
    depends_on:
      - api  # api arranca antes
    environment:
      - API_URL=http://api:8080  # URL interna del backend

  # Backend API
  api:
    build: ./backend
    ports:
      - "8080:8080"
    depends_on:
      - mysql
      - redis
    environment:
      - DB_HOST=mysql  # nombre del servicio, no localhost
      - DB_PORT=3306
      - DB_USER=root
      - DB_PASSWORD=dev123  # en producción usa .env
      - REDIS_HOST=redis
      - REDIS_PORT=6379

  # MySQL
  mysql:
    image: mysql:8.0
    ports:
      - "3306:3306"
    environment:
      - MYSQL_ROOT_PASSWORD=dev123
      - MYSQL_DATABASE=myapp
    volumes:
      - mysql_data:/var/lib/mysql

  # Redis
  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"

volumes:
  mysql_data:

Campos clave

services: cada servicio puede usar build, image o ambos.

ports: "puerto_host:puerto_contenedor". Si 3306 está ocupado, usa "13006:3306".

depends_on: orden de arranque, con matices (ver abajo).

environment: variables de conexión. En producción no dejes contraseñas en texto plano.

volumes: persistencia; borrar el contenedor no borra el volumen.

Trampa habitual

El nombre del servicio es el hostname en la red interna. DB_HOST=mysql, no localhost. En el contenedor de la API, localhost es la propia API, no MySQL. La primera vez que escribí localhost:3306 no conectó hasta que usé el nombre del servicio.

Dependencias y orden de arranque

depends_on ordena contenedores, no garantiza que MySQL ya acepte conexiones. El contenedor puede estar up mientras el daemon aún inicializa.

Solución 1: healthcheck

services:
  mysql:
    image: mysql:8.0
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      interval: 5s
      timeout: 3s
      retries: 10

  api:
    depends_on:
      mysql:
        condition: service_healthy

Solución 2: reintentos en la aplicación

Más simple: reintentar la conexión con backoff exponencial. Node.js con pool de mysql2; Python con tenacity.

Personalmente prefiero reintentos en la app: menos YAML y suele bastar. Healthcheck para servicios muy lentos.

Solución 1: healthcheck (detalle)

services:
  mysql:
    image: mysql:8.0
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      interval: 5s
      timeout: 3s
      retries: 10

  api:
    depends_on:
      mysql:
        condition: service_healthy

Solución 2: reintentos en código

Node.js con pool mysql2:

const pool = mysql.createPool({
  host: 'mysql',
  port: 3306,
  user: 'root',
  password: 'dev123',
  database: 'myapp',
  waitForConnections: true,
  connectionLimit: 10,
  queueLimit: 0,
});

Python con tenacity:

from tenacity import retry, stop_after_attempt, wait_exponential

@retry(stop=stop_after_attempt(5), wait=wait_exponential(multiplier=1, min=2, max=10))
def connect_db():
    return mysql.connector.connect(host='mysql', ...)

Estrategia multi-entorno

Dev, test y prod difieren en puertos expuestos, contraseñas y servicios externos. Compose resuelve esto con base + override.

Base (común)

# docker-compose.yml
version: "3.8"

services:
  web:
    build: ./frontend

  api:
    build: ./backend
    environment:
      - DB_HOST=mysql
      - REDIS_HOST=redis

  mysql:
    image: mysql:8.0
    volumes:
      - mysql_data:/var/lib/mysql

  redis:
    image: redis:7-alpine

volumes:
  mysql_data:

Override de desarrollo

# docker-compose.override.yml
version: "3.8"

services:
  web:
    ports:
      - "3000:3000"

  api:
    ports:
      - "8080:8080"
    environment:
      - DEBUG=true

  mysql:
    ports:
      - "3306:3306"
    environment:
      - MYSQL_ROOT_PASSWORD=dev123

  redis:
    ports:
      - "6379:6379"

docker-compose up fusiona ambos archivos automáticamente.

Override de producción

# docker-compose.prod.yml
version: "3.8"

services:
  web:
    # sin ports; nginx como proxy

  api:
    environment:
      - DB_HOST={{DB_HOST}}
      - DB_PASSWORD={{DB_PASSWORD}}

  mysql:
    environment:
      - MYSQL_ROOT_PASSWORD={{MYSQL_ROOT_PASSWORD}}
docker-compose -f docker-compose.yml -f docker-compose.prod.yml up -d

Variables desde .env (no commitear):

environment:
  - DB_HOST={{DB_HOST:-localhost}}
  - DB_PASSWORD={{DB_PASSWORD:-dev123}}

Comandos esenciales

docker-compose up -d      # arrancar en segundo plano
docker-compose ps         # estado

Salida típica de docker-compose ps:

NAME                COMMAND             SERVICE   STATUS    PORTS
myapp-web-1         "npm start"         web       running   0.0.0.0:3000->3000/tcp
myapp-api-1         "node index.js"     api       running   0.0.0.0:8080->8080/tcp
myapp-mysql-1       "mysqld"            mysql     running   0.0.0.0:3306->3306/tcp
myapp-redis-1       "redis-server"      redis     running   0.0.0.0:6379->6379/tcp
docker-compose logs -f api   # -f sigue el log en vivo
docker-compose down          # parar; conserva volúmenes
docker-compose down -v       # parar y borrar volúmenes
docker-compose build api
docker-compose up -d --build api
ComandoUso
docker-compose up -dArrancar todo
docker-compose psVer estado
docker-compose logs -f apiLogs de API
docker-compose downParar contenedores
docker-compose down -vParar y borrar datos
docker-compose restart apiReiniciar servicio
docker-compose build apiReconstruir imagen

Cierre

OperaciónManualCompose
Onboarding4-8 horas5 minutos (clone + up)
Cambiar proyectoReconfigurar, parar serviciosCambiar directorio
LimpiarDesinstalar, buscar procesosUn comando
Consistencia en equipoCada máquina distintaMismo YAML

Si sigues instalando bases de datos a mano, prueba Compose con API + MySQL. Luego añade Redis, RabbitMQ y multi-entorno.

Commitea docker-compose.yml y docker-compose.override.yml con un README. Un comando y listo — más fiable que documentación que caduca.

Orquestación multi-servicio con Docker Compose

Orquesta Web, API, MySQL y Redis con Docker Compose para arrancar el entorno local con un comando

⏱️ Estimated time: 15 min

  1. 1

    Step 1: Crear docker-compose.yml

    Crea el archivo de configuración en la raíz del proyecto:

    ```yaml
    version: "3.8"
    services:
    web:
    build: ./frontend
    ports: ["3000:3000"]
    depends_on: [api]
    api:
    build: ./backend
    ports: ["8080:8080"]
    depends_on: [mysql, redis]
    mysql:
    image: mysql:8.0
    environment:
    MYSQL_ROOT_PASSWORD: dev123
    MYSQL_DATABASE: myapp
    redis:
    image: redis:7-alpine
    ```

    Nota: entre contenedores usa el nombre del servicio (p. ej. DB_HOST=mysql), no localhost
  2. 2

    Step 2: Arrancar todos los servicios

    En el directorio de docker-compose.yml ejecuta:

    ```bash
    docker-compose up -d
    ```

    • La primera vez tarda más al descargar imágenes
    • Arranques posteriores reutilizan imágenes locales y son casi instantáneos
    • Sin -d los logs ocupan la terminal
  3. 3

    Step 3: Verificar el estado

    Comprueba que todos los contenedores estén en marcha:

    ```bash
    docker-compose ps
    ```

    • STATUS running indica normalidad
    • Si aparece exited o error, revisa con:
    ```bash
    docker-compose logs api
    ```
  4. 4

    Step 4: Configurar multi-entorno (opcional)

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

    ```yaml
    version: "3.8"
    services:
    mysql:
    ports: ["3306:3306"]
    ```

    • Compose fusiona override automáticamente
    • En producción usa -f:
    ```bash
    docker-compose -f docker-compose.yml -f docker-compose.prod.yml up -d
    ```
  5. 5

    Step 5: Limpiar el entorno

    Detener y eliminar contenedores:

    ```bash
    docker-compose down # conserva volúmenes
    docker-compose down -v # elimina volúmenes (borra datos)
    ```

    • down al cambiar de proyecto
    • down -v si los datos están corruptos y quieres empezar de cero

FAQ

¿Qué diferencia hay entre docker-compose.yml y Dockerfile?
Dockerfile define cómo construir una imagen; docker-compose.yml define cómo varios contenedores trabajan juntos. En resumen: Dockerfile crea imágenes, Compose gestiona contenedores. Un proyecto suele tener uno o varios Dockerfile y un docker-compose.yml.
¿depends_on garantiza que el servicio esté listo?
No. depends_on solo ordena el arranque de contenedores, no espera a que MySQL acepte conexiones. Soluciones: añadir healthcheck en docker-compose.yml o reintentos en el código de la aplicación. Los reintentos en la app suelen ser más simples y fiables.
¿Cómo accede un contenedor a servicios del host?
Usa host.docker.internal (Docker Desktop) o la IP del host (Linux). Si Redis corre en el host, desde el contenedor conecta a host.docker.internal:6379. En Linux host.docker.internal requiere configuración extra; --network host es otra opción pero pierdes aislamiento de red.
¿Dónde se guardan los datos? ¿Se pierden al borrar contenedores?
Los datos en volumes persisten en volúmenes gestionados por Docker; borrar contenedores no los elimina. docker-compose down solo quita contenedores y redes. Para vaciar datos: docker-compose down -v. En desarrollo es habitual cuando quieres resetear.
¿Cómo gestionar configuraciones dev/test/prod?
Archivo base + overrides. docker-compose.yml con lo común, docker-compose.override.yml para dev (Compose lo carga por defecto), docker-compose.prod.yml para producción. Arranca con: docker-compose -f docker-compose.yml -f docker-compose.prod.yml up -d.
¿Qué hacer si un puerto está ocupado?
Cambia el mapeo en el host. Si 3306 está ocupado, usa 13006:3306 y conecta a localhost:13006. O elimina ports y accede solo por la red interna de contenedores, más seguro.

5 min de lectura · Publicado el: 9 abr 2026 · Actualizado el: 21 ago 2026

Comentarios

Inicia sesión con GitHub para dejar un comentario

Easton BlogEaston Blog