Con el fichero ya escrito, toca dominar la herramienta que lo interpreta. La CLI de Compose tiene unos veinticinco subcomandos, pero el reparto es muy desigual: usarás cinco a diario, otros cinco a menudo y el resto en situaciones concretas.

Esta lección los recorre por familias, siempre sobre el compose.yaml de Aurora Libros de la lección anterior. Presta especial atención a dos parejas que se confunden sin parar: up frente a start, y exec frente a run.

Contenido

  1. Tabla maestra de subcomandos
  2. up: el comando que reconcilia
  3. down y el peligro de -v
  4. start, stop, restart, pause, create
  5. Observación: ps, logs, top, stats, events
  6. Ejecución puntual: exec frente a run
  7. Construcción y publicación: build, pull, push
  8. Escalado local con --scale y sus límites
  9. Diagnóstico: config y validación en CI
  10. Selección de ficheros y proyecto: -f, -p, COMPOSE_FILE
  11. Autocompletado y alias útiles

  1. Tabla maestra de subcomandos

Familia Comando Qué hace
Ciclo de vida up Crea o actualiza y arranca todo lo declarado
down Para y elimina contenedores y redes del proyecto
create Crea los contenedores sin arrancarlos
start / stop Arranca o para contenedores ya existentes
restart Para y vuelve a arrancar sin releer el fichero
pause / unpause Congela y descongela los procesos (SIGSTOP)
kill Envía una señal (SIGKILL por defecto)
rm Elimina contenedores parados
Observación ps Estado de los servicios del proyecto
logs Registros agregados por servicio
top Procesos dentro de cada contenedor
stats Consumo de recursos en vivo
events Flujo de eventos del proyecto
port Puerto del host asociado a un puerto interno
Ejecución exec Comando en un contenedor en marcha
run Comando en un contenedor nuevo
Imágenes build Construye los servicios con build:
pull / push Descarga o publica las imágenes
images Imágenes usadas por el proyecto
Diagnóstico config Valida y muestra la configuración final
version Versión de Compose
ls Lista todos los proyectos de la máquina
cp Copia ficheros entre host y servicio
wait Bloquea hasta que un servicio termine

Un comando que se olvida y es utilísimo: docker compose ls funciona desde cualquier directorio y te dice qué proyectos hay levantados en la máquina y con qué fichero.

docker compose ls
NAME            STATUS       CONFIG FILES
aurora-libros   running(4)   /home/joan/aurora-libros/compose.yaml

  1. up: el comando que reconcilia

docker compose up es el 80 % del uso diario. No es "arrancar": es llevar el estado real al estado declarado. En cada ejecución, para cada servicio, Compose calcula un resumen de su configuración (el config-hash que viste en la lección 04-01) y decide:

Situación Acción de Compose
El contenedor no existe Lo crea y lo arranca
Existe y la configuración coincide No lo toca
Existe pero cambió el fichero o la imagen Lo recrea
Existe pero está parado Lo arranca
Existe y no está en el fichero Lo deja (salvo --remove-orphans)

Esa selectividad es lo que hace que editar la memoria de la caché y ejecutar up -d recree solo la caché y no tire abajo la base de datos.

Opción Efecto
-d, --detach En segundo plano. En la práctica, siempre
--build Construye antes de arrancar los servicios con build:
--no-build Falla si falta una imagen en lugar de construir
--pull always Descarga la imagen aunque exista en local
--force-recreate Recrea todo aunque nada haya cambiado
--no-recreate No recrea nada aunque haya cambiado
--no-deps Ignora depends_on: solo el servicio pedido
--wait Espera a que los servicios estén healthy antes de devolver el control
--wait-timeout 60 Segundos máximos de espera de --wait
--remove-orphans Elimina contenedores del proyecto que ya no están en el fichero
--abort-on-container-exit Para todo si un contenedor termina (útil en CI)
--scale s=N Arranca N réplicas del servicio s
cd ~/aurora-libros
docker compose up -d --build --wait
[+] Building 2/2
[+] Running 4/4
 ✔ Container aurora-libros-aurora-db-1     Healthy
 ✔ Container aurora-libros-aurora-cache-1  Healthy
 ✔ Container aurora-libros-aurora-api-1    Healthy
 ✔ Container aurora-libros-aurora-web-1    Healthy

--wait es la opción que convierte a Compose en una herramienta de automatización: sin ella, el comando devuelve el control en cuanto los contenedores están arrancados, y un script de CI que lanza pruebas inmediatamente después se estrella contra una base de datos que aún no acepta conexiones. Con --wait, Compose no devuelve el control hasta que todas las sondas de salud pasan, y sale con código distinto de cero si alguna no lo consigue.

Sin -d, up deja el terminal enganchado a los logs agregados de todos los servicios y Ctrl+C para la pila entera. Es cómodo para depurar un arranque; peligroso si lo olvidas en una sesión SSH.

Un caso muy útil de --no-deps: recrear solo la API sin reiniciar la base de datos.

docker compose up -d --no-deps --force-recreate aurora-api

  1. down y el peligro de -v

down es la inversa de up: para y elimina contenedores y redes del proyecto.

Comando Contenedores Redes Volúmenes con nombre Volúmenes anónimos Imágenes
down Elimina Elimina Conserva Elimina Conserva
down -v Elimina Elimina BORRA Elimina Conserva
down --rmi local Elimina Elimina Conserva Elimina Borra las sin etiqueta propia
down --rmi all Elimina Elimina Conserva Elimina Borra todas las del proyecto
down --remove-orphans Elimina también los huérfanos Elimina Conserva Elimina Conserva
stop Conserva (parados) Conserva Conserva Conserva Conserva

Grábate la segunda fila. docker compose down -v borra los volúmenes con nombre del proyecto sin preguntar: en Aurora Libros, eso es el catálogo entero de la librería. Es exactamente lo que quieres al resetear un entorno de desarrollo y exactamente lo que arruina un servidor si lo tecleas por inercia. Dos defensas: declarar los volúmenes críticos como external: true (lección 04-02) y no crear jamás un alias de shell que incluya -v.

docker compose down --timeout 30    # margen de apagado ordenado, por servicio

  1. start, stop, restart, pause, create

Comando ¿Lee el fichero? ¿Recrea? Cuándo usarlo
up -d Si hay cambios Siempre que toques el compose.yaml
start No Nunca Volver a arrancar lo ya creado
stop No No Parar sin destruir
restart No No Reiniciar el proceso, sin aplicar cambios
pause No No Congelar procesos liberando CPU, no memoria
create Si hay cambios Crear sin arrancar

La trampa de restart merece un aviso explícito: no relee el fichero. Si cambias una variable de entorno y ejecutas docker compose restart aurora-api, el contenedor se reinicia con la configuración antigua y te vuelves loco buscando por qué no se aplica el cambio. Para aplicar cambios del fichero, siempre up -d.

docker compose stop aurora-cache      # parar un servicio concreto
docker compose start aurora-cache     # volver a arrancarlo
docker compose restart aurora-web     # reinicio rápido de nginx
docker compose kill -s SIGHUP aurora-web   # recargar configuración sin reiniciar

  1. Observación: ps, logs, top, stats, events

docker compose ps
docker compose ps -a                 # incluye los parados y los que terminaron
docker compose ps --services         # solo nombres de servicio, uno por línea
docker compose ps --status running
docker compose ps --format "table {{.Service}}\t{{.Status}}\t{{.Ports}}"
docker compose ps --format json | jq -r '.[] | "\(.Service): \(.Health)"'
aurora-api: healthy
aurora-cache: healthy
aurora-db: healthy
aurora-web: healthy

--services es oro para guiones: for s in $(docker compose ps --services); do ...; done.

Los logs son la herramienta que más usarás:

docker compose logs                       # todos los servicios, agregados
docker compose logs -f aurora-api         # seguir uno en tiempo real
docker compose logs --tail 50 aurora-db   # últimas 50 líneas
docker compose logs --since 10m           # de los últimos 10 minutos
docker compose logs -t --no-color api web # con marca de tiempo, varios servicios

Compose colorea y prefija cada línea con el nombre del servicio, así que docker compose logs -f con la pila entera te deja ver el flujo de una petición atravesando web → api → db. Recuerda de la lección 03-04 que solo verás lo que los procesos escriban en stdout/stderr.

docker compose top aurora-db     # procesos dentro del contenedor
docker compose stats --no-stream # consumo puntual de todos los servicios
docker compose events --json     # flujo de eventos del proyecto
docker compose port aurora-web 80
0.0.0.0:8080

port responde a "¿en qué puerto del host está publicado el 80 de la web?", y es imprescindible cuando dejas que Docker asigne puertos aleatorios.

  1. Ejecución puntual: exec frente a run

Es la distinción que más confusión genera, y la respuesta es simple: exec entra en un contenedor que ya está corriendo; run crea uno nuevo.

Aspecto exec run
Contenedor El existente del servicio Uno nuevo, con sufijo -run-<hash>
Requisito El servicio debe estar en marcha El servicio no necesita estar arrancado
Dependencias Irrelevante Arranca las de depends_on (evítalo con --no-deps)
Puertos Los que ya tiene No publica los ports (salvo --service-ports)
Al terminar El contenedor sigue vivo Queda parado, salvo --rm
Uso típico Inspeccionar, psql, redis-cli, shell Tareas puntuales: migraciones, pruebas, seeds
# EXEC: hablar con la base de datos que está sirviendo ahora mismo
docker compose exec aurora-db psql -U aurora -d aurora_libros
docker compose exec aurora-db psql -U aurora -d aurora_libros -c "SELECT count(*) FROM libros;"
docker compose exec aurora-cache redis-cli INFO keyspace
docker compose exec aurora-api sh                    # shell dentro de la API
docker compose exec -u root aurora-api sh            # como root, para instalar algo
docker compose exec -T aurora-db pg_dump -U aurora aurora_libros > copia.sql

-T desactiva la asignación de pseudo-TTY: obligatorio cuando rediriges la salida a un fichero o encadenas tuberías, o el volcado saldrá con caracteres de control.

# RUN: contenedores nuevos y efímeros
docker compose run --rm aurora-api npm test
docker compose run --rm --no-deps aurora-api node -e "console.log(process.version)"
docker compose run --rm --entrypoint sh aurora-api
docker compose run --rm -e NODE_ENV=test aurora-api npm run test:integracion
docker compose run --rm --service-ports aurora-api    # publica sus puertos
> [email protected] test
> node --test

✔ GET /salud devuelve 200 (12.4ms)
✔ GET /libros devuelve 9 títulos (31.7ms)
✔ GET /libros/:id inexistente devuelve 404 (4.1ms)
ℹ pass 3

--rm casi siempre. Sin él, cada run deja un contenedor parado en la máquina; al cabo de una semana tienes cuarenta contenedores aurora-libros-aurora-api-run-a3f9c2 ocupando espacio. Y --no-deps cuando la tarea no necesita la pila: sin él, un run inocente te levanta PostgreSQL y Redis.

  1. Construcción y publicación: build, pull, push

docker compose build                       # construye los servicios con build:
docker compose build --no-cache aurora-api # ignora la caché de capas
docker compose build --pull                # refresca la imagen base primero
docker compose build --progress plain      # salida completa, útil para depurar
docker compose pull                        # descarga las imágenes declaradas
docker compose pull --ignore-buildable     # omite los servicios que se construyen
docker compose push aurora-api             # publica en el registro
docker compose images
CONTAINER                       REPOSITORY                  TAG      SIZE
aurora-libros-aurora-api-1      auroralibros/aurora-api     1.2.0    142MB
aurora-libros-aurora-cache-1    redis                       7-alpine 41.4MB
aurora-libros-aurora-db-1       postgres                    16-alpine 274MB
aurora-libros-aurora-web-1      nginx                       alpine   48.3MB

El flujo típico de CI, que verás completo en la lección 06-02, cabe en tres líneas: docker compose build, docker compose push y, en el servidor, docker compose pull && docker compose up -d.

  1. Escalado local con --scale y sus límites

docker compose up -d --scale aurora-api=3
docker compose ps --format "table {{.Name}}\t{{.Ports}}"
Error response from daemon: driver failed programming external connectivity:
Bind for 0.0.0.0:3000 failed: port is already allocated

Ahí está el primer choque: un puerto fijo del host no se puede repartir entre tres réplicas. Solo hay un 3000 en la máquina. Y si el servicio tuviera container_name, fallaría antes todavía, porque dos contenedores no pueden llamarse igual.

Obstáculo Por qué choca Solución
ports: ["3000:3000"] Un solo puerto del host Quitar el puerto o usar - "3000" (aleatorio)
container_name Nombres duplicados No usar container_name
Volumen de datos compartido Dos Postgres sobre los mismos ficheros No escalar servicios con estado

Quitando la publicación fija, funciona:

docker compose up -d --scale aurora-api=3
docker compose ps --services --filter status=running | sort | uniq -c
docker compose exec aurora-web getent hosts aurora-api
172.20.0.5   aurora-api
172.20.0.7   aurora-api
172.20.0.8   aurora-api

El DNS interno de Docker devuelve las tres direcciones para el mismo nombre, y el cliente elige una. Es un reparto rudimentario —sin comprobación de salud, sin pesos, sin reintentos— que sirve para pruebas locales, no para producción. El balanceo real, con sus estrategias y su gestión de réplicas caídas, es la lección 06-06.

docker compose up -d --scale aurora-api=1    # volver a una réplica

  1. Diagnóstico: config y validación en CI

docker compose config lee todos los ficheros implicados, interpola variables, fusiona overrides y muestra la configuración final tal y como la entiende Compose. Es tu única forma de ver la verdad.

docker compose config                  # configuración completa resuelta
docker compose config --quiet          # solo valida: sin salida, código 0 o 1
docker compose config --services       # nombres de los servicios
docker compose config --volumes        # nombres de los volúmenes
docker compose config --images         # imágenes que se usarán
docker compose config --no-interpolate # deja los ${...} sin resolver
docker compose config --format json | jq '.services["aurora-api"].deploy'
{ "resources": { "limits": { "memory": "268435456", "cpus": "1.0" } } }

En una tubería de CI, --quiet es la primera comprobación que debe correr, antes incluso de construir nada:

docker compose config --quiet || { echo "compose.yaml inválido"; exit 1; }

Dos avisos sobre config: resuelve las variables, así que su salida puede contener contraseñas en claro (nunca la vuelques en un log público), y muestra el resultado normalizado, con las formas cortas convertidas en formas largas. Esa normalización es justo lo que necesitas para entender qué hizo una fusión de anclas o de ficheros.

  1. Selección de ficheros y proyecto: -f, -p, COMPOSE_FILE

Las opciones globales van antes del subcomando:

docker compose -f ~/aurora-libros/compose.yaml ps        # correcto
docker compose ps -f ~/aurora-libros/compose.yaml        # ERROR
Opción Qué hace
-f, --file Fichero a usar. Repetible, y el orden importa
-p, --project-name Nombre del proyecto (prefijo de todos los objetos)
--project-directory Directorio base para resolver rutas relativas
--profile Activa un perfil (lección 04-06)
--env-file Fichero de variables para la interpolación (lección 04-05)
docker compose -f compose.yaml -f compose.prod.yaml -p aurora-prod up -d

Con varios -f, Compose fusiona los ficheros en el orden dado y el último gana en caso de conflicto. El mecanismo de fusión completo, con sus reglas por tipo de clave, es la lección 04-06.

Un detalle que sorprende: cuando usas -f, el directorio base para las rutas relativas pasa a ser el del primer fichero. Si tus ficheros están repartidos, --project-directory te deja fijarlo explícitamente.

Y la variable de entorno equivalente, útil para no repetir -f cien veces al día:

export COMPOSE_FILE=compose.yaml:compose.prod.yaml   # separador ":" en Linux/macOS
export COMPOSE_PROJECT_NAME=aurora-prod
docker compose up -d      # usa ambos ficheros y el nombre de proyecto

  1. Autocompletado y alias útiles

# Bash: completado de subcomandos, servicios y opciones
docker completion bash | sudo tee /etc/bash_completion.d/docker > /dev/null
# Zsh
docker completion zsh > "${fpath[1]}/_docker"

Con el autocompletado activo, docker compose logs -f aur<TAB> te ofrece los servicios reales del proyecto: se acabó teclear mal los nombres.

alias dc='docker compose'
alias dcu='docker compose up -d'
alias dcl='docker compose logs -f --tail 100'
alias dcp='docker compose ps'
alias dce='docker compose exec'
# Deliberadamente NO existe un alias con "down -v"

Ese último comentario no es una broma: la mayor parte de las pérdidas de datos con Compose vienen de un alias corto que alguien escribió con -v "para limpiar rápido".

Errores Comunes y Consejos

Usar restart esperando que aplique cambios del fichero. No lo hace. Cambio en el compose.yamlup -d, siempre.

Confundir stop con down. stop conserva los contenedores, down los elimina junto con la red. Si tras un down esperabas encontrar el contenedor parado, no está.

Teclear down -v por costumbre. Borra los volúmenes con nombre y con ellos los datos. Piénsalo cada vez.

Olvidar --rm en run. Cada ejecución deja un contenedor parado. Limpia los acumulados con docker compose rm -f.

Ejecutar exec con redirección sin -T. El pseudo-TTY inserta caracteres de control y corrompe volcados y ficheros binarios.

Poner -f después del subcomando. Las opciones globales van antes; el error que devuelve la CLI no siempre lo deja claro.

Consejo: cuando algo no se comporta como esperas, el orden de diagnóstico es docker compose config (¿qué entiende Compose?), docker compose ps (¿qué está corriendo?) y docker compose logs (¿qué dice el servicio?). En ese orden resuelves casi todo.

Ejercicios

Ejercicio 1. Con la pila levantada, cambia el límite de memoria de aurora-cache de 256M a 320M y aplícalo sin que se reinicien los otros tres servicios. Demuestra con comandos que solo se recreó la caché y que el límite nuevo está activo.

Ejercicio 2. Escribe un guion verificar.sh apto para CI que: valide el fichero, levante la pila esperando a que esté sana, ejecute las pruebas de la API en un contenedor efímero, imprima el estado de los cuatro servicios y limpie todo, volúmenes incluidos, devolviendo el código de salida de las pruebas.

Ejercicio 3. Sin apagar la pila, obtén: (a) el número de claves en Redis, (b) los tres primeros títulos del catálogo ordenados alfabéticamente, y (c) un volcado de la tabla libros en el fichero ~/libros.sql del host. Explica qué opción es imprescindible en el tercer caso y por qué.

Soluciones

Solución 1.

docker compose ps --format "{{.Service}}: {{.RunningFor}}"
aurora-api: 22 minutes ago
aurora-cache: 22 minutes ago
aurora-db: 22 minutes ago
aurora-web: 22 minutes ago

Edita el límite en el compose.yaml (limits: { memory: 320M, cpus: "0.5", pids: 100 }) y aplica:

docker compose up -d
 ✔ Container aurora-libros-aurora-db-1     Running
 ✔ Container aurora-libros-aurora-cache-1  Recreated
 ✔ Container aurora-libros-aurora-api-1    Running
 ✔ Container aurora-libros-aurora-web-1    Running

Ahí está la reconciliación: Recreated solo en la caché, Running en los otros tres porque su config-hash no cambió. Verificación:

docker compose ps --format "{{.Service}}: {{.RunningFor}}"
docker inspect aurora-libros-aurora-cache-1 --format '{{.HostConfig.Memory}}'
aurora-cache: 4 seconds ago
335544320

La caché lleva cuatro segundos y los demás siguen con sus veintidós minutos. Y 335544320 bytes son exactamente 320 MiB. Ojo: recrear un contenedor no es reiniciarlo, es destruirlo y crear otro; los datos que no estén en un volumen se pierden. En Redis, eso significa una caché vacía, que aquí es inocuo porque /libros la repuebla desde la base de datos.

Solución 2.

#!/usr/bin/env bash
# verificar.sh — pipeline local de Aurora Libros
set -uo pipefail
cd "$(dirname "$0")"

docker compose config --quiet || { echo "compose.yaml inválido"; exit 1; }

docker compose up -d --build --wait --wait-timeout 90 || {
  echo "La pila no llegó a estado sano"
  docker compose logs --tail 40
  docker compose down -v
  exit 1
}

docker compose run --rm --no-deps aurora-api npm test
codigo=$?

docker compose ps --format "table {{.Service}}\t{{.Status}}"
docker compose down -v --remove-orphans
exit $codigo

Cuatro decisiones importantes: --quiet valida antes de gastar tiempo construyendo; --wait con --wait-timeout garantiza que las pruebas no arranquen contra una base de datos que aún no responde y falla con código distinto de cero si no lo consigue; --rm --no-deps en las pruebas evita dejar basura y no vuelve a levantar dependencias que ya están sanas; y down -v es aquí correcto y deseable, porque es un entorno desechable. Fíjate en que se guarda $? antes de los comandos de limpieza: si no, el código de salida que devuelve el guion sería el de down, y el pipeline pasaría siempre en verde.

Solución 3.

# (a) claves en la caché
docker compose exec aurora-cache redis-cli DBSIZE
# (b) tres primeros títulos
docker compose exec aurora-db psql -U aurora -d aurora_libros \
  -c "SELECT titulo FROM libros ORDER BY titulo LIMIT 3;"
# (c) volcado al host
docker compose exec -T aurora-db pg_dump -U aurora -t libros aurora_libros > ~/libros.sql
(integer) 1
                  titulo
------------------------------------------
 Cien años de soledad
 El Aleph
 El jardín de senderos que se bifurcan
(3 rows)

En el tercer caso -T es imprescindible. Sin él, Compose asigna un pseudo-TTY al comando y el flujo de salida deja de ser binario limpio: se traducen los saltos de línea y se cuelan secuencias de control, con lo que el .sql resultante puede no volver a importarse. La regla es sencilla: si la salida no la vas a leer tú con los ojos, -T.

Conclusión

Ya manejas la CLI completa. Sabes que up no es "arrancar" sino "reconciliar": compara el resumen de configuración de cada servicio con el declarado y recrea solo lo que cambió, lo que te permite ajustar un límite y tocar un único contenedor. Conoces sus opciones decisivas —-d, --build, --pull, --force-recreate, --no-deps, --remove-orphans y sobre todo --wait, que espera a que las sondas de salud pasen y falla si no lo hacen—. Y tienes claro qué borra cada variante de down, con -v marcado en rojo.

Distingues start/stop/restart/pause de up/down, con la trampa de que restart no relee el fichero. Observas con ps (incluidos --services y --format json), logs, top, stats, events y port. Y tienes interiorizada la diferencia clave: exec entra en lo que ya corre, run crea un contenedor nuevo, con --rm para no acumular basura, --no-deps para no levantar media pila y -T cuando rediriges la salida. Construyes y publicas con build/pull/push, escalas localmente con --scale sabiendo que choca con los puertos fijos y con container_name, validas con config --quiet en CI y controlas qué fichero y qué proyecto usas con -f, -p y COMPOSE_FILE.

En la lección siguiente, Aplicaciones Multi-Contenedor, todo esto se pone al servicio del montaje definitivo: la arquitectura de Aurora Libros con dos redes segmentadas —una frontal y una trasera marcada como internal—, el DNS de Compose y la tabla de quién habla con quién y por qué puerto, el orden de arranque resuelto con depends_on y condition: service_healthy usando pg_isready y redis-cli ping, un servicio de migración con service_completed_successfully, y el patrón de reintento con backoff que hace falta aunque tengas todo lo anterior. Al final, los quince pasos de onboarding se quedarán en dos.

Docker: De Principiante a Avanzado

Módulo 1: Introducción a Docker

Módulo 2: Trabajando con Imágenes Docker

Módulo 3: Contenedores Docker

Módulo 4: Docker Compose

Módulo 5: Conceptos Avanzados de Docker

Módulo 6: Docker en Producción

Módulo 7: Ecosistema y Herramientas de Docker

© Copyright 2026. Todos los derechos reservados