PTENES
MÓDULO 4.2

🎼 Docker Compose: varios servicios

Una aplicación real casi nunca es un solo contenedor: está la app, la base de datos y quizá una caché. Docker Compose es la herramienta que administra todos esos contenedores a la vez, con un solo archivo y un solo comando.

6
Temas
45
Minutos
Básico
Nivel
Práctico
Tipo
1

Qué es Docker Compose

En el módulo anterior subiste un contenedor a la vez, manualmente, con docker run. Funciona para un contenedor. Pero ¿qué pasa cuando tu sitio necesita tres al mismo tiempo: la app, la base de datos y una caché? ¿Escribir tres comandos enormes cada vez, recordar cada puerto, cada volumen y cada conexión? Olvídalo. El Docker Compose resuelve eso: describes todo en un archivo y subes toda la stack con un solo comando.

docker-compose.yml services: app: base de datos: cache: up -d red de Compose (los contenedores se reconocen por el nombre) app node :3000 base de datos postgres :5432 cache redis :6379 💾 volume (datos) Un archivo describe toda la stack. Un comando lo inicia todo: docker compose up -d

🧠 Analogía: el director de la orquesta

Imagina una orquesta. Cada músico (contenedor) sabe tocar su instrumento por su cuenta: la app toca, la base de datos toca, la caché toca. Pero si cada uno empieza cuando quiere, el resultado es ruido. El maestro (Compose) lee la partitura (el archivo docker-compose.yml) y los rige a todos juntos: quién empieza primero, quién habla con quién y en qué tono.

  • •A partitura = el archivo docker-compose.yml
  • •Los músicos = cada servicio (app, base de datos, caché)
  • •O "empezar a sonar" = docker compose up

💡 Docker Compose ya viene incluido

Si instalaste Docker moderno (Docker Desktop o el Docker Engine reciente en Linux), Compose ya está instalado como un subcomando: lo llamas docker compose (con espacio). En máquinas antiguas, el comando era docker-compose (con guion). Ambos hacen lo mismo; en este módulo usamos la forma nueva con espacio.

# Comprobar si Compose está disponible

$ docker compose version

Docker Compose version v2.27.0

2

El archivo docker-compose.yml básico

Toda la magia está en un único archivo de texto llamado docker-compose.yml. Está escrito en YAML, un formato sencillo basado en indentación (espacios al comienzo de la línea). La regla de oro: la indentación indica qué está dentro de qué. Vamos de lo más sencillo a lo más completo.

El compose más pequeño posible: un servicio

# docker-compose.yml

services:

app:

image: nginx:alpine

ports:

- "8080:80"

Esto describe un servicio llamado app, que usa la imagen nginx:alpine y expone el puerto 80 del container en el puerto 8080 de tu máquina.

services

La lista de todos los containers del stack. Cada clave aquí dentro es un servicio.

image

La imagen lista para usar (de Docker Hub). Alternativa: build: para construir a partir de tu Dockerfile.

ports

Mapea los puertos en el formato "host:container". La de la izquierda y la de tu PC.

💡 Sobre el "version:" en la parte superior

En los tutoriales antiguos verás una línea version: "3.8" al principio del archivo. En Compose moderno (v2), esta línea es opcional y se ignora: puedes empezar directamente en services:. Si un archivo de ejemplo tiene el version:, no te preocupes, sigue funcionando.

⚠️ Error común

Problema: "yaml: line 4: mapping values are not allowed in this context" o el stack no se inicia.
Solución: Casi siempre es indentación incorrecta. YAML no acepta Tab, solo espacios. Mantén siempre 2 espacios por nivel y nunca mezcles Tab con espacios. En VS Code, activa «mostrar espacios en blanco» para ver el problema.

✓ Qué HACER

  • ✓Usar siempre 2 espacios por nivel de indentación
  • ✓Poner versiones fijas en las imágenes (nginx:1.27)
  • ✓Dar nombres claros a los servicios (app, banco)

✗ Qué NO hacer

  • ✗Usar Tab para indentar (rompe el YAML)
  • ✗Usar solo :latest en producción (se vuelve una sorpresa)
  • ✗Olvidar las comillas en "8080:80"
3

Servicios, redes y volúmenes

Tres conceptos resuelven el 90% de los casos: servicios (los contenedores), redes (cómo se comunican) y volúmenes (dónde se guardan los datos). La gran ventaja de Compose: crea una red automática para tu stack y, dentro de ella, un servicio llama al otro por su nombre.

👁 Qué vas a ver: comunicación por nombre

No usas una IP. Si el servicio de la base de datos se llama banco, la app se conecta a él mediante la URL, usando el nombre del servicio como dirección:

# Dentro del contenedor de la app, la conexión con la base de datos es así:

DATABASE_URL=postgres://user:senha@base de datos:5432/meubanco

# "banco" es el nombre del servicio, NO una IP. Compose lo resuelve.

Red: por defecto, ya está lista

Al iniciar la stack, Compose crea una red solo para ella. Todos los servicios se unen a esa red automáticamente y se reconocen por su nombre. Solo declaras redes manualmente cuando quieres aislar grupos (por ejemplo: separar el frontend del backend).

services:

app:

image: mi-app

depends_on:

- banco # garantiza que la base de datos se inicie antes

Volumen: para que los datos no desaparezcan

El contenedor es desechable: si eliminas el contenedor de la base de datos, los datos se van con él. El volumen guarda los datos fuera del contenedor, en un lugar que sobrevive a su recreación. Decláralo en la raíz y úsalo dentro del servicio:

services:

base de datos:

image: postgres:16

volúmenes:

- datos-db:/var/lib/postgresql/data

# Declaración del volumen en la raíz del archivo:

volúmenes:

datos-db:

Formato: nome-do-volume:/caminho/dentro/do/container. Aunque elimines y vuelvas a crear el contenedor, los datos siguen ahí.

⚠️ Error común

Problema: "Cada vez que ejecuto docker compose down mi base de datos se reinicia."
Solución: Faltó el volumen. Sin un volumen, los datos quedan dentro del contenedor y desaparecen cuando lo eliminas. Declara un volumen con nombre para la base de datos. Ten cuidado también con down -v: o -v borra los volúmenes intencionalmente.

4

Subir un stack completo: app + base de datos

Llegó la hora de unirlo todo. Vamos a montar una stack real: un app web que se comunica con un base de datos PostgreSQL, con un volumen para que los datos no desaparezcan. Un archivo, un comando, dos contenedores comunicándose.

El docker-compose.yml completo

# docker-compose.yml

services:

app:

build: . # construye a partir del Dockerfile local

ports:

- "3000:3000"

environment:

- DATABASE_URL=postgres://user:senha@base de datos:5432/app

depends_on:

- base de datos

base de datos:

image: postgres:16

environment:

- POSTGRES_USER=user

- POSTGRES_PASSWORD=senha

- POSTGRES_DB=app

volúmenes:

- datos-db:/var/lib/postgresql/data

volúmenes:

datos-db:

1

Subir todo el stack

O -d ("detached") deja los contenedores ejecutándose en segundo plano

$ docker compose up -d

[+] Running 3/3

✓ Network meuapp_default Creada

✓ Container meuapp-banco-1 Iniciado

✓ Container meuapp-app-1 Iniciado

2

Verificar que se haya subido

Lista los contenedores del stack y el estado de cada uno

$ docker compose ps

NAME SERVICE STATUS PORTS

meuapp-app-1 app Up 12 seconds 0.0.0.0:3000->3000/tcp

meuapp-banco-1 banco Up 13 seconds 5432/tcp

3

Detener cuando termines

Detiene y elimina los contenedores y la red (pero mantiene el volumen de datos)

$ docker compose down

[+] Running 3/3

✓ Container meuapp-app-1 Removed

✓ Container meuapp-banco-1 Eliminado

✓ Network meuapp_default Eliminada

💡 Consejo: up sin -d para verlo todo de una vez

La primera vez, ejecuta docker compose up sin o -d. Los registros de todos los servicios aparecen mezclados en pantalla, en tiempo real. Es ideal para ver si algo dio error al iniciarse. Cuando todo esté bien, detén todo con Ctrl+C y vuelve a subir con -d.

5

Logs y depuración: cuando algo falla

¿Subiste el stack y el sitio no abre? Tranquilo, es normal. Compose te da tres herramientas para investigar: ver los registros (lo que dice cada servicio), ver el estado de los contenedores, y entrar de un contenedor para verlo de cerca.

logs - Leer lo que dicen los servicios

# Logs de todos los servicios, siguiéndolos en vivo (-f = follow)

$ docker compose logs -f

banco-1 | database system is ready to accept connections

app-1 | Server listening on port 3000

# Logs de UN solo servicio

$ docker compose logs app

# Solo las últimas 50 líneas

$ docker compose logs --tail 50 banco

El log es el primer lugar que debes revisar. 9 de cada 10 problemas aparecen aquí en texto claro.

ps - Ver el estado de cada contenedor

$ docker compose ps

NAME SERVICE STATUS PORTS

meuapp-app-1 app Exited (1) 4 seconds ago

meuapp-banco-1 banco Up 20 seconds 5432/tcp

Aquí el app está como Exited (1): se cayó. El siguiente paso es revisar su registro con docker compose logs app.

🔎 exec - Entrar en un contenedor

A veces necesitas mirar por dentro: ejecutar un comando, abrir el cliente de la base de datos, revisar un archivo. El exec ejecuta un comando dentro de un contenedor que ya está en marcha:

# Abrir una terminal dentro del contenedor de la app

$ docker compose exec app sh

/app # ls

node_modules package.json server.js

# Abrir el cliente de PostgreSQL dentro de la base de datos

$ docker compose exec banco psql -U user -d app

app=# \dt

⚠️ Error común

Problema: "La app inicia, pero no se conecta a la base de datos: connection refused."
Solución: O depends_on garantiza la orden de subida, pero no espera a que la base de datos quede listo para aceptar conexiones (tarda algunos segundos). Solución simple: haz que la app intente reconectarse algunas veces. Solución robusta: usa healthcheck en la base de datos + condition: service_healthy en depends_on.

6

Actualización y rollback

Tu aplicación está en línea y quieres publicar una nueva versión. O peor: ya la publicaste y algo salió mal, necesitas volver rápido. Con Compose, actualizar y volver a una versión anterior requiere pocos comandos. El secreto es fijar la etiqueta de la imagen para saber siempre qué versión está en ejecución.

Actualizar a una nueva versión

Supón que tu imagen en el archivo era meu-app:1.4 y es la nueva versión y la 1.5. Cambias la etiqueta en el archivo y actualizas solo lo que cambió:

# 1. Editar el compose: image: meu-app:1.4 -> image: meu-app:1.5

# 2. Descargar la nueva imagen del registry

$ docker compose pull

✓ app Descargada

# 3. Recrear solo los contenedores que cambiaron

$ docker compose up -d

[+] Running 2/2

✓ Container meuapp-banco-1 En ejecución

✓ Container meuapp-app-1 Recreated

Observa: la base de datos queda Running (no se modificó) y solo la app y Recreated. Compose solo modifica lo que cambió.

↻

Rollback: volver a la versión anterior

¿Algo salió mal en la 1.5? Vuelve la etiqueta a 1.4 y vuelve a subir. Como fijaste la versión, sabes exactamente a cuál volver.

# Editar el compose para volver atrás: image: meu-app:1.5 -> image: meu-app:1.4

$ docker compose up -d

✓ Container meuapp-app-1 Recreated

# Listo: en segundos vuelves a la versión 1.4

✓ Qué HACER

  • ✓Fijar etiquetas de versión (app:1.4) para poder volver
  • ✓Ejecutar pull antes de up -d
  • ✓Guardar el compose en Git (cada versión se convierte en historial)

✗ Qué NO hacer

  • ✗Usar :latest (no se puede saber a qué versión volver)
  • ✗Ejecutar down -v pensando que solo hay que reiniciar
  • ✗Actualizar directamente en producción sin probar antes

🏆 Ya puedes ejecutar una stack de verdad

Con la app, la base de datos y el volumen en un archivo, todo iniciándose con un comando, leyendo logs, entrando en los contenedores y volviendo a una versión anterior cuando hace falta, ya tienes lo esencial de la orquestación local. En el próximo módulo aprenderás a hacer que esta stack iniciarse sola cuando se enciende el servidor, con el systemd.

📚 Resumen del módulo

✓
Compose y el maestro - administra varios contenedores con un archivo y un comando
✓
docker-compose.yml - YAML con indentación por espacios; comienza en services:
✓
Servicios, redes y volúmenes - se comunican por nombre; el volumen guarda los datos
✓
up -d / down / ps / logs / exec - iniciar, detener, ver el estado, leer los logs, entrar
✓
pull + up -d y volver a la etiqueta - actualizar y hacer rollback fijando versiones

Siguiente módulo:

4.3 - Systemd: hacer que tu stack se inicie sola cuando arranca el servidor y se reinicie si se cae