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 - 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 - 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.27rather than:latest. Reproducible builds depend on it. - Healthchecks - both containers expose them so
depends_on.condition: service_healthyis meaningful. - Named volumes -
db_datasurvivesdocker compose down; bind mounts would not. - Named networks - explicit
frontend/backendnetworks 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 (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:
# 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:
# 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:
# 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 -
.yamlor.yml. See YML vs YAML for the full comparison. - If both
compose.yamlanddocker-compose.ymlexist in the same directory, Compose loadscompose.yaml.
Validate before you ship
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.