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.

Servicios, red del proyecto, volumen con nombre y puertos asignados al host en un proyecto de Compose
Un proyecto de Compose: el nombre del servicio también es el nombre del host, y solo se puede acceder desde el host a los servicios que tienen configurado ports

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 a compose.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_isready con -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:

ValorSignificado
service_startedEl contenedor se ha iniciado (valor predeterminado)
service_healthyLa comprobación de estado se ha superado
service_completed_successfullyEl 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 ejecutar docker compose up en local.
  • compose.prod.yaml: para producción; se especifica con docker compose -f compose.yaml -f compose.prod.yaml up -d.

Resumen

  • image usa una imagen existente; build construye una imagen a partir de un Dockerfile.
  • Solo depends_on junto con condition: service_healthy espera a que el servicio esté realmente listo.
  • profiles permite iniciar servicios como las herramientas de administración solo cuando los necesitas.
  • compose.override.yaml se combina automáticamente; usa docker compose config para 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