Skip to main content

Last updated: April 28, 2026

Docker Compose YAML

Complete reference for compose.yaml and docker-compose.yml: services, networks, volumes, env_file, depends_on with healthchecks, profiles, and anchor reuse - with copy-ready examples.

Written by Mohan Raj Kolavi.

Quick answer

A Docker Compose file declares a multi-container application in YAML. The recommended filename is compose.yaml (older docker-compose.yml still works). Define containers under services:, shared resources under top-level networks:, volumes:, and secrets:, then run docker compose up -d. The top-level version: field is obsolete and should be omitted.

Minimal compose.yaml

The smallest useful Compose file: a web server and a database. No version: field is required - it has been obsolete since Compose v2:

compose.yaml
# compose.yaml - minimal web + database
services:
  web:
    image: nginx:1.27
    ports:
      - "8080:80"
  db:
    image: postgres:16
    environment:
      POSTGRES_PASSWORD: secret

Run docker compose up -d in the directory to bring the stack online, docker compose ps to see the running containers, and docker compose down to tear it down.

Production-grade example

A more complete file with build context, healthchecks, named volumes, named networks, secrets, and image pinning:

compose.yaml (full)
# compose.yaml - production-grade example
services:
  web:
    build:
      context: .
      dockerfile: Dockerfile
    image: myorg/web:1.4.0
    ports:
      - "80:8080"
    environment:
      NODE_ENV: production
    env_file:
      - .env
    depends_on:
      db:
        condition: service_healthy
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
      interval: 30s
      timeout: 5s
      retries: 3
    networks:
      - frontend
      - backend

  db:
    image: postgres:16
    volumes:
      - db_data:/var/lib/postgresql/data
    environment:
      POSTGRES_DB: app
      POSTGRES_USER: app
      POSTGRES_PASSWORD_FILE: /run/secrets/db_password
    secrets:
      - db_password
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U app"]
      interval: 10s
      timeout: 5s
      retries: 5
    networks:
      - backend

volumes:
  db_data:

networks:
  frontend:
  backend:

secrets:
  db_password:
    file: ./secrets/db_password.txt

Highlights:

  • Image pinning - postgres:16, nginx:1.27 rather than :latest. Reproducible builds depend on it.
  • Healthchecks - both containers expose them sodepends_on.condition: service_healthy is meaningful.
  • Named volumes - db_data survivesdocker compose down; bind mounts would not.
  • Named networks - explicit frontend / backend networks let you isolate which services can talk to each other.
  • Secrets - the database password lives in a file the container reads at startup, not in the compose file.

env_file vs environment

environment: sets variables inline. Use it for values that belong with the service definition. env_file: loads from an external file - use it for secrets and per-environment differences:

.env
# .env (sibling of compose.yaml)
NODE_ENV=production
DATABASE_URL=postgres://app:secret@db:5432/app
LOG_LEVEL=info

Compose loads .env from the directory of the compose file. Inline environment: values override env_file values for the same key.

depends_on with healthchecks

Plain depends_on only controls start order - the dependent container starts as soon as the dependency starts, not when it is ready. For a database, that is too soon. Pair with a healthcheck and the service_healthy condition:

depends-on-health.yaml
# Wait for db to be healthy before starting api
services:
  db:
    image: postgres:16
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U app"]
      interval: 10s
      timeout: 5s
      retries: 5

  api:
    image: myorg/api:1.4.2
    depends_on:
      db:
        condition: service_healthy

Profiles for optional services

A profile gates a service behind a CLI flag. Use profiles for dev-only tooling, optional workers, or debug containers that should not start by default:

profiles.yaml
# Run subsets of services with --profile
services:
  web:
    image: myorg/web:latest
    # always runs - no profile

  worker:
    image: myorg/worker:latest
    profiles: ["worker"]

  debug:
    image: nicolaka/netshoot
    profiles: ["debug"]
    network_mode: "service:web"
# docker compose up                  # only web
# docker compose --profile worker up # web + worker
# docker compose --profile debug up  # web + debug

DRY with YAML anchors

Compose supports YAML anchors and merge keys directly. The x- prefix tells Compose to ignore the top-level key, so it exists only to host the anchor:

anchor-reuse.yaml
# DRY: share env across services with anchors
x-shared-env: &shared-env
  TZ: UTC
  LOG_LEVEL: info

services:
  web:
    image: myorg/web:latest
    environment:
      <<: *shared-env
      ROLE: web

  worker:
    image: myorg/worker:latest
    environment:
      <<: *shared-env
      ROLE: worker

Full reference on the YAML anchors page, including merging multiple anchors and the deep-merge gotcha.

compose.yaml vs docker-compose.yml

  • compose.yaml - canonical filename per the Compose Specification. Use it for new projects.
  • docker-compose.yml - legacy filename. Still recognized by Docker Compose v2 for backward compatibility.
  • Either extension works - .yaml or .yml. See YML vs YAML for the full comparison.
  • If both compose.yaml and docker-compose.yml exist in the same directory, Compose loads compose.yaml.

docker compose config resolves the file (including anchor expansion and env substitution) and prints the canonical YAML the engine will use - the fastest way to spot bugs. For syntax-level checks, drop the file into the YAML validator. For Kubernetes-style manifests, see the Kubernetes YAML page.

Frequently Asked Questions

What is a Docker Compose YAML file?

A Docker Compose YAML file (compose.yaml or docker-compose.yml) declares a multi-container application. It lists services, networks, volumes, and secrets in YAML, and 'docker compose up' brings the whole stack online with one command. Compose is the standard for local development and lightweight production deployments.

Should I use compose.yaml or docker-compose.yml?

Both work. Modern Compose (v2.x and the Compose Specification) recommends 'compose.yaml' as the canonical filename. 'docker-compose.yml' is still recognized for backward compatibility. If both exist, compose.yaml wins. New projects should use compose.yaml.

Do I need a 'version' field in docker-compose.yml?

No. As of Docker Compose v2 and the Compose Specification, the top-level 'version:' field is obsolete and ignored. Older guides and Stack Overflow answers still show it - safely delete it. The Compose engine version is determined by the Docker Compose binary, not the file.

How do I define a service in Docker Compose YAML?

Add a key under 'services:' with the service name. At minimum, set 'image:' (a registry image) or 'build:' (a path to a Dockerfile). Add 'ports', 'environment', 'volumes', and 'depends_on' as needed. Each service becomes one or more running containers.

How do I pass environment variables in docker-compose.yml?

Three options. Inline under 'environment:' as a map ('KEY: value') or list ('- KEY=value'). Reference an external file with 'env_file: .env' (or a list of files) - Compose loads it relative to the compose file. For host shell vars, write '${VAR}' and Compose substitutes at parse time.

What is the difference between 'env_file' and 'environment' in Compose?

'env_file' loads variables from a .env-style file into the container at runtime. 'environment' sets variables inline in the compose file. Inline values override env_file values for the same key. env_file is best for secrets and per-environment differences; environment is best for variables that belong with the service definition.

How do volumes work in Docker Compose?

Two flavors. A bind mount maps a host path to a container path: 'volumes: - ./src:/app'. A named volume is managed by Docker and survives 'docker compose down': 'volumes: - db_data:/var/lib/postgresql/data', plus a top-level 'volumes: db_data: {}'. Use named volumes for databases.

How do I make one Compose service wait for another?

Use 'depends_on' with a 'condition'. 'service_started' waits until the dependency starts. 'service_healthy' waits until its healthcheck passes - this is what you want for databases. Without a condition, depends_on only controls start order, not readiness.

What are Docker Compose profiles?

A profile is a tag that gates a service. By default, services with no profile always run. A service marked 'profiles: ["worker"]' only runs when you pass '--profile worker' on the command line. Use profiles to keep dev-only or optional services in the same compose file without starting them by default.

Can I share configuration across Docker Compose services?

Yes. Use YAML anchors and merge keys. Define a shared block under a top-level extension key like 'x-shared-env: &shared-env { ... }', then merge it into each service with 'environment: <<: *shared-env'. Compose ignores 'x-' prefixed top-level keys, so they exist only to host the anchor.

How do I run multiple Compose files together?

Pass them with '-f': 'docker compose -f compose.yaml -f compose.prod.yaml up'. Later files override earlier ones for matching keys. The standard pattern is one base compose.yaml plus per-environment override files (compose.dev.yaml, compose.prod.yaml) committed alongside.

Why does docker-compose say 'YAML syntax error'?

Most often: tab characters in indentation (YAML rejects tabs - use spaces), an unquoted value that looks like a number or boolean (port '8080:80' must be quoted because of the colon), or inconsistent indentation between siblings. Run the file through a YAML validator to find the exact line.

Docker Compose YAML: compose.yaml & docker-compose.yml Guide (2026) | Kolavi Studio