Contenido
Tutorial de sintaxis de compose.yaml: aprende a configurar Docker Compose
El nivel superior de compose.yaml tiene principalmente cuatro secciones:
name: myapp # Nombre del proyecto (opcional, el valor predeterminado es el nombre de la carpeta)
services: # Contenedores que se van a ejecutar
...
volumes: # Volúmenes con nombre
...
networks: # Red personalizada (opcional, se crea una automáticamente de forma predeterminada)
...
A continuación se explican los ajustes más habituales dentro de services según su función. Al final, ejecutarás un ejemplo completo.
image y build
Usa una imagen existente:
services:
redis:
image: redis:8-alpine
Construye una imagen desde un Dockerfile:
services:
app:
build: . # Usa el Dockerfile de la carpeta actual
Si necesitas más opciones:
services:
app:
build:
context: ./backend
dockerfile: Dockerfile.prod
target: prod # Etapa objetivo de la compilación multietapa
args:
APP_VERSION: "1.4.2"
image: myorg/app:1.4.2 # Nombre de la imagen después de construirla
Si escribes build e image a la vez, Compose asigna al resultado de la compilación el valor de image. Después puedes ejecutar docker compose push.
ports: puertos
ports:
- "8080:80" # Host:contenedor
- "127.0.0.1:5432:5432" # Solo accesible localmente
- "9000-9002:9000-9002" # Rango
Ponlos entre comillas. YAML interpreta algunos valores con formato número:número como números en base 60, lo que puede causar errores extraños.
expose solo describe el propósito; no publica el puerto al exterior. Los servicios de una misma red ya pueden comunicarse entre sí.
volumes: montajes
services:
db:
image: postgres:18
volumes:
- pgdata:/var/lib/postgresql # Volumen con nombre
- ./init.sql:/docker-entrypoint-initdb.d/init.sql:ro # Bind mount (ruta relativa)
volumes:
pgdata:
-
Las rutas que comienzan por
./o../son Bind mounts relativos acompose.yaml. -
Un nombre sin ruta es un volumen con nombre. Debes declararlo en
volumes:en el nivel superior. El nombre real tendrá como prefijo el nombre del proyecto (por ejemplo,shop_pgdata). -
Para usar un volumen creado fuera de Compose:
volumes: pgdata: external: true
environment y env_file
environment:
NODE_ENV: production
DATABASE_URL: postgres://app:${DB_PASSWORD}@db:5432/app
env_file:
- .env.app
Los valores de ${...} provienen del archivo .env o de las variables de entorno actuales. Consulta Variables de entorno y archivos .env.
depends_on y healthcheck: espera a que la base de datos esté lista
depends_on solo controla el orden de inicio: garantiza que el contenedor db se inicie primero, pero no garantiza que la base de datos ya acepte conexiones. PostgreSQL tarda unos segundos en iniciarse, así que la aplicación puede fallar al intentar conectarse. Si usas healthcheck y condition: service_healthy, Compose esperará a que la comprobación de estado se supere antes de iniciar la aplicación.
Ejemplo práctico (compose.yaml):
name: shop
services:
app:
image: postgres:18
command: ["psql", "-h", "db", "-U", "shop", "-c", "SELECT 'app connected' AS status;"]
environment:
PGPASSWORD: ${DB_PASSWORD}
depends_on:
db:
condition: service_healthy
db:
image: postgres:18
environment:
POSTGRES_USER: shop
POSTGRES_PASSWORD: ${DB_PASSWORD:?DB_PASSWORD is required}
POSTGRES_DB: shop
volumes:
- pgdata:/var/lib/postgresql
healthcheck:
test: ["CMD-SHELL", "pg_isready -h 127.0.0.1 -U shop -d shop"]
interval: 2s
timeout: 3s
retries: 20
adminer:
image: adminer
ports:
- "8081:8080"
profiles: [tools]
volumes:
pgdata:
app usa el cliente de PostgreSQL para simular una aplicación que «se conecta a la base de datos al iniciarse». En .env, configura DB_PASSWORD=change-me.
Inicia solo db y observa cómo cambia su estado:
$ docker compose up -d db
[+] up 3/3
✔ Network shop_default Created 0.0s
✔ Volume shop_pgdata Created 0.0s
✔ Container shop-db-1 Started 0.3s
$ docker compose ps
NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS
shop-db-1 postgres:18 "docker-entrypoint.s…" db 1 second ago Up Less than a second (health: starting) 127.0.0.1:5432->5432/tcp
Unos segundos después:
$ docker compose ps
NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS
shop-db-1 postgres:18 "docker-entrypoint.s…" db 10 seconds ago Up 9 seconds (healthy) 127.0.0.1:5432->5432/tcp
El estado cambia de health: starting a healthy. Detén los contenedores y vuelve a iniciar app desde cero:
$ docker compose up app
[+] up 3/3
✔ Network shop_default Created 0.0s
✔ Container shop-db-1 Created 0.0s
✔ Container shop-app-1 Created 0.1s
Attaching to app-1
Container shop-db-1 Waiting
Container shop-db-1 Healthy
app-1 | status
app-1 | ---------------
app-1 | app connected
app-1 | (1 row)
app-1 |
app-1 exited with code 0
w Enable Watch d Detach
Compose inicia primero db, muestra Waiting y espera hasta que aparezca Healthy para iniciar app. La primera conexión de app se realiza correctamente (app connected). Sin condition: service_healthy, app intentaría conectarse antes de que la base de datos estuviera lista.
(La última línea, w Enable Watch d Detach, muestra los atajos de teclado de Compose cuando se ejecuta en primer plano.)
La comprobación de estado usa
pg_isreadycon-h 127.0.0.1. Sin esa opción, la base de datos se consideraría healthy durante la inicialización y la aplicación no podría conectarse; consulta la prueba en PostgreSQL. MySQL tiene el mismo problema; consulta MySQL.
Valores disponibles para condition:
| Valor | Significado |
|---|---|
service_started | El contenedor se ha iniciado (valor predeterminado) |
service_healthy | La comprobación de estado se ha superado |
service_completed_successfully | El servicio ha terminado de ejecutarse con código de salida 0; es adecuado para tareas puntuales, como las migraciones de bases de datos |
También puedes añadir restart: true bajo depends_on para que app se reinicie cuando se reinicie db.
restart
restart: unless-stopped
Valores disponibles: no, always, on-failure, unless-stopped (consulta el comportamiento en Ciclo de vida del contenedor).
command y entrypoint
Sobrescribe CMD / ENTRYPOINT de la imagen:
command: ["npm", "run", "dev"]
entrypoint: ["/app/docker-entrypoint.sh"]
networks
De forma predeterminada, todos los servicios están en la misma red nombre_del_proyecto_default. Si necesitas aislarlos, puedes definir redes personalizadas:
services:
web:
image: nginx:1.30-alpine
networks: [frontend]
app:
build: .
networks: [frontend, backend]
db:
image: postgres:18
networks: [backend]
networks:
frontend:
backend:
web puede conectarse a app, pero no a db.
profiles: servicios de inicio opcional
En el ejemplo anterior, adminer tiene configurado profiles: [tools]:
$ docker compose config --services
db
app
$ docker compose --profile tools config --services
db
app
adminer
Si no especificas un perfil, adminer no se inicia; solo lo hará si añades --profile tools. Es útil para servicios que solo necesitas iniciar a veces, como herramientas de administración o servidores de correo para pruebas.
Límites de recursos y otros ajustes habituales
services:
app:
image: myorg/app:1.4.2
container_name: app # fixed container name (cannot be scaled later)
user: "1000:1000"
working_dir: /app
mem_limit: 512m
cpus: 1.5
init: true # same as docker run --init
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
extra_hosts:
- "host.docker.internal:host-gateway"
compose.override.yaml: configuración combinada
Compose combina automáticamente compose.yaml y compose.override.yaml. En la carpeta de ejemplo también está este archivo:
# compose.override.yaml
services:
db:
ports:
- "127.0.0.1:5432:5432"
Usa docker compose config para ver la configuración final combinada (fíjate en el ports del final):
$ docker compose config db
name: shop
services:
db:
environment:
POSTGRES_DB: shop
POSTGRES_PASSWORD: change-me
POSTGRES_USER: shop
image: postgres:18
networks:
default: null
volumes:
- type: volume
source: pgdata
target: /var/lib/postgresql
volume: {}
healthcheck:
test:
- CMD-SHELL
- pg_isready -h 127.0.0.1 -U shop -d shop
timeout: 3s
interval: 2s
retries: 20
ports:
- mode: ingress
host_ip: 127.0.0.1
target: 5432
published: "5432"
protocol: tcp
networks:
default:
name: shop_default
volumes:
pgdata:
name: shop_pgdata
Si especificas explícitamente un solo archivo con -f compose.yaml, el override no se combina y ports desaparece:
$ docker compose -f compose.yaml config db
name: shop
services:
db:
environment:
POSTGRES_DB: shop
POSTGRES_PASSWORD: change-me
POSTGRES_USER: shop
image: postgres:18
networks:
default: null
volumes:
- type: volume
source: pgdata
target: /var/lib/postgresql
volume: {}
healthcheck:
test:
- CMD-SHELL
- pg_isready -h 127.0.0.1 -U shop -d shop
timeout: 3s
interval: 2s
retries: 20
networks:
default:
name: shop_default
volumes:
pgdata:
name: shop_pgdata
docker compose config también expande la sintaxis abreviada al formato completo (por ejemplo, pgdata:/var/lib/postgresql pasa a ser type: volume, source y target), y sustituye ${DB_PASSWORD}. Si parece que una configuración no se aplica, el primer paso es usar este comando para comprobar qué contenido está leyendo realmente Compose.
Una división habitual:
compose.yaml: configuración compartida por todos los entornos.compose.override.yaml: configuración exclusiva para desarrollo (montar el código fuente, abrir puertos de depuración); se aplica automáticamente al ejecutardocker compose upen local.compose.prod.yaml: para producción; se especifica condocker compose -f compose.yaml -f compose.prod.yaml up -d.
Resumen
imageusa una imagen existente;buildconstruye una imagen a partir de un Dockerfile.- Solo
depends_onjunto concondition: service_healthyespera a que el servicio esté realmente listo. profilespermite iniciar servicios como las herramientas de administración solo cuando los necesitas.compose.override.yamlse combina automáticamente; usadocker compose configpara revisar la configuración final.
Ejercicios
Ejercicio 1: Añade restart: unless-stopped al servicio db de este capítulo y limita la memoria a 512 MB.
Respuesta
db:
image: postgres:18
restart: unless-stopped
mem_limit: 512m
Después de modificarlo, ejecuta docker compose up -d para aplicar los cambios.
Ejercicio 2: adminer tiene configurado profiles: [tools]. ¿Cómo lo inicias junto con los demás servicios?
Respuesta
docker compose --profile tools up -d
Preguntas frecuentes
¿Por qué depends_on no espera a que la base de datos esté lista?
Escribir solo depends_on: [db] garantiza únicamente el orden de inicio. Para esperar a que se pueda conectar con la base de datos, necesitas añadir healthcheck y condition: service_healthy. En este capítulo y en [PostgreSQL](/es/docker/postgresql/) se explica cómo configurar la comprobación de estado.
¿Qué ocurre si la indentación de YAML es incorrecta?
docker compose config mostrará un error, por ejemplo did not find expected key. Consulta [Mensajes de error comunes](/es/docker/errors/). Usa solo espacios, no tabuladores.
¿Sigue siendo necesario escribir version: '3.8'?
No. La especificación de Compose ya no utiliza el campo version; las versiones nuevas lo ignoran y muestran una advertencia.
Actualizado 14 de octubre de 2026