Overview
Docker Compose defines multi-container applications in a single YAML file. This cheat sheet covers the structure of compose.yaml, every service key, environment and configuration options, networking and volumes, the full CLI, override files, and profiles. All examples target Docker Compose v2 (the docker compose plugin) and the Compose Specification; the legacy version: key is obsolete and ignored.
compose.yaml structure
The basic structure of a Compose file:
services: # Define containerized services
volumes: # Define named volumes
networks: # Define custom networks
configs: # Define configs (for swarm, optional)
secrets: # Define secrets (for swarm, optional)
Services: basic keys
| Key | Purpose | Example |
|---|---|---|
image: <name>:<tag> |
Use a pre-built image | image: nginx:1.27 |
build: <path> |
Build an image from a Dockerfile in the given path | build: . or build: ./services/web |
build: { context: <path>, dockerfile: <file> } |
Build with explicit context and Dockerfile path | build: { context: ., dockerfile: Dockerfile.prod } |
build: { context: <path>, args: { VAR: value } } |
Pass build args | build: { context: ., args: { NODE_ENV: production } } |
container_name: <name> |
Set the container name (not recommended for scaling) | container_name: my_web_app |
restart: <policy> |
Restart policy: no, always, on-failure, unless-stopped |
restart: unless-stopped |
restart: on-failure:<max-retries> |
Restart on failure up to N times | restart: on-failure:5 |
Networking and ports
| Key | Purpose | Example |
|---|---|---|
ports: [<host>:<container>] |
Map container port to host | ports: ["8080:80"] |
ports: [<host-ip>:<host>:<container>] |
Map to specific host IP | ports: ["127.0.0.1:8080:80"] |
ports: [<container>] |
Expose without mapping to host | ports: ["8080"] |
expose: [<port>] |
Expose port for internal communication (no host mapping) | expose: ["8080"] |
networks: [<network>] |
Attach service to custom networks | networks: [frontend, backend] |
networks: <network>: { ipv4_address: <ip> } |
Assign static IP | networks: { backend: { ipv4_address: 172.20.0.2 } } |
hostname: <name> |
Set container hostname | hostname: db-server |
links: [<service>] |
Link to another service (legacy, use networks instead) | links: [db] |
dns: [<ip>] |
Set DNS servers | dns: [8.8.8.8, 8.8.4.4] |
dns_search: [<domain>] |
Set DNS search domains | dns_search: [example.com] |
Environment and configuration
| Key | Purpose | Example |
|---|---|---|
environment: { VAR: value } |
Set environment variables inline | environment: { DEBUG: "true", LOG_LEVEL: info } |
environment: [VAR=value] |
Set environment variables as a list | environment: [DEBUG=true, LOG_LEVEL=info] |
env_file: <file> |
Load environment variables from a file | env_file: .env |
env_file: [<file1>, <file2>] |
Load from multiple files (later files override earlier ones) | env_file: [.env, .env.local] |
working_dir: <path> |
Set working directory inside the container | working_dir: /app |
command: <command> |
Override the CMD from Dockerfile | command: python -u app.py |
command: [<cmd>, <arg1>, <arg2>] |
Command as a list (exec form, preferred) | command: [python, -u, app.py] |
entrypoint: <cmd> |
Override ENTRYPOINT from Dockerfile | entrypoint: /bin/bash |
entrypoint: [<cmd>, <arg>] |
ENTRYPOINT as a list (exec form, preferred) | entrypoint: [python, -u] |
stdin_open: true |
Keep stdin open even if not attached | stdin_open: true |
tty: true |
Allocate a pseudo-terminal | tty: true |
Storage: volumes and bind mounts
| Key | Purpose | Example |
|---|---|---|
volumes: [<host>:<container>] |
Bind mount a host directory | volumes: [./data:/app/data] |
volumes: [<name>:<container>] |
Mount a named volume | volumes: [my_data:/app/data] |
volumes: { <container>: {} } |
Anonymous volume | volumes: { /app/data: {} } |
volumes: { <container>: { ro: true } } |
Read-only mount | volumes: { /data: { ro: true } } |
volumes: { <container>: { consistency: <mode> } } |
Consistency mode (macOS/Windows only) | volumes: { /data: { consistency: delegated } } |
tmpfs: [<path>] |
Mount a temporary filesystem | tmpfs: [/tmp] |
Note: Define named volumes at the top level:
volumes:
my_data:
driver: local
Resource limits
| Key | Purpose | Example |
|---|---|---|
cpus: <float> |
Limit CPU cores (e.g., 0.5 for half a core) |
cpus: 2 |
cpu_shares: <int> |
CPU share weight (relative priority) | cpu_shares: 512 |
mem_limit: <value> |
Limit memory (e.g., 512m, 1g) |
mem_limit: 512m |
memswap_limit: <value> |
Limit memory + swap | memswap_limit: 1g |
mem_reservation: <value> |
Soft memory limit (allows exceeding if space available) | mem_reservation: 256m |
Health checks
| Key | Purpose | Example |
|---|---|---|
healthcheck: { test: [CMD, curl, -f, 'http://localhost/health'] } |
Define a health check command | (see below) |
healthcheck: { test: CMD curl -f http://localhost/health } |
Health check as a string | healthcheck: { test: CMD curl -f http://localhost/health } |
healthcheck: { test: CMD-SHELL, 'pg_isready -U postgres' } |
Health check with shell | (see below) |
healthcheck: { interval: 30s } |
How often to run the check (default: 30s) | interval: 30s |
healthcheck: { timeout: 5s } |
Timeout for the check (default: 30s) | timeout: 5s |
healthcheck: { start_period: 10s } |
Grace period before first check (default: 0s) | start_period: 10s |
healthcheck: { retries: 3 } |
Consecutive failures before marking unhealthy (default: 3) | retries: 3 |
healthcheck: disable: true |
Disable health check (if inherited from image) | disable: true |
Full example:
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
interval: 30s
timeout: 5s
start_period: 10s
retries: 3
Dependencies and startup order
| Key | Purpose | Example |
|---|---|---|
depends_on: [<service>] |
Wait for services to start (no readiness check) | depends_on: [db, cache] |
depends_on: { <service>: { condition: service_started } } |
Wait for service to start | depends_on: { db: { condition: service_started } } |
depends_on: { <service>: { condition: service_healthy } } |
Wait for service to be healthy | depends_on: { db: { condition: service_healthy } } |
depends_on: { <service>: { condition: service_completed_successfully } } |
Wait for service to complete successfully (one-off jobs) | depends_on: { migration: { condition: service_completed_successfully } } |
Caution: condition: service_healthy requires the service to have a healthcheck. Without it, the condition fails immediately.
Full example:
services:
web:
depends_on:
db:
condition: service_healthy
cache:
condition: service_started
Security: secrets and configs
| Key | Purpose | Example |
|---|---|---|
secrets: [<name>] |
Mount a secret file in /run/secrets/<name> |
secrets: [db_password] |
configs: [<name>] |
Mount a config file in / |
configs: [nginx_config] |
Top-level definition:
secrets:
db_password:
file: ./secrets/db_password.txt
configs:
nginx_config:
file: ./nginx.conf
Access in container:
# In shell: cat /run/secrets/db_password
Profiles
Profiles allow you to conditionally include services.
services:
web:
image: nginx:latest
# Always included
debug:
image: busybox:latest
profiles: [debug]
# Only included with --profile debug
monitoring:
image: prom/prometheus:latest
profiles: [monitoring, prod]
# Included with --profile monitoring OR --profile prod
Usage:
docker compose up # Include only services without profiles
docker compose --profile debug up # Include web + debug
docker compose --profile monitoring up # Include web + monitoring
docker compose --profile debug --profile monitoring up # Include all three
Networks (top-level)
networks:
frontend:
driver: bridge
backend:
driver: bridge
driver_opts:
com.docker.network.bridge.name: br0
Service usage:
services:
web:
networks: [frontend, backend]
db:
networks: [backend]
Volumes (top-level)
volumes:
db_data:
driver: local
shared_cache:
driver: local
driver_opts:
type: tmpfs
device: tmpfs
Override files
Docker Compose automatically loads docker-compose.override.yaml and merges it with compose.yaml.
compose.yaml (production):
services:
web:
image: myapp:latest
environment:
LOG_LEVEL: warn
docker-compose.override.yaml (local development):
services:
web:
build: .
ports:
- "8000:8000"
environment:
LOG_LEVEL: debug
Result when running docker compose up:
– Image is built from ./Dockerfile (overrides image: myapp:latest)
– Port 8000 is exposed on the host
– LOG_LEVEL is set to debug
– Port mapping and LOG_LEVEL are merged
Explicit override file:
docker compose -f compose.yaml -f compose.prod.yaml up
CLI commands
| Command | Purpose | Example |
|---|---|---|
docker compose up |
Create and start all services | docker compose up |
docker compose up -d |
Start services in detached mode (background) | docker compose up -d |
docker compose up -d --scale web=3 |
Scale a service to N replicas | docker compose up -d --scale web=3 |
docker compose down |
Stop and remove all services, networks (keeps volumes) | docker compose down |
docker compose down -v |
Stop and remove services, networks, AND volumes | docker compose down -v |
docker compose down --rmi all |
Remove services and delete all images | docker compose down --rmi all |
docker compose down --remove-orphans |
Remove services not in the Compose file | docker compose down --remove-orphans |
docker compose ps |
List running services | docker compose ps |
docker compose logs |
View logs from all services | docker compose logs |
docker compose logs -f |
Stream logs continuously (follow) | docker compose logs -f |
docker compose logs -f <service> |
Stream logs from one service | docker compose logs -f web |
docker compose logs --tail 50 <service> |
Show last 50 lines from a service | docker compose logs --tail 50 web |
docker compose exec <service> <command> |
Run a command inside a service | docker compose exec web bash |
docker compose exec -it <service> bash |
Run bash interactively inside a service | docker compose exec -it db psql -U postgres |
docker compose exec -u <user> <service> <command> |
Run as a specific user | docker compose exec -u appuser web whoami |
docker compose pull |
Pull the latest images for all services | docker compose pull |
docker compose build |
Build all images (if build: is specified) |
docker compose build |
docker compose build --no-cache <service> |
Rebuild without cache | docker compose build --no-cache web |
docker compose start |
Start stopped services | docker compose start |
docker compose stop |
Stop running services | docker compose stop |
docker compose stop <service> |
Stop a specific service | docker compose stop web |
docker compose restart |
Restart all services | docker compose restart |
docker compose restart <service> |
Restart a specific service | docker compose restart web |
docker compose pause |
Pause all services | docker compose pause |
docker compose unpause |
Resume paused services | docker compose unpause |
docker compose config |
Validate and print the final Compose config | docker compose config |
docker compose config --resolve-image-digests |
Resolve image digests (useful for reproducibility) | docker compose config --resolve-image-digests |
docker compose watch |
Watch for changes and sync/rebuild services (Compose 2.22+) | docker compose watch |
docker compose version |
Show Docker Compose version | docker compose version |
docker compose -f <file> <command> |
Explicitly specify a Compose file | docker compose -f docker-compose.prod.yaml up |
docker compose --project-name <name> <command> |
Use a custom project name (default: directory name) | docker compose --project-name myapp up |
Patterns you’ll reuse
# Development with override file
# compose.yaml: production setup
# docker-compose.override.yaml: local dev tweaks
# Database with healthcheck and depends_on
services:
db:
image: postgres:16
environment:
POSTGRES_PASSWORD: secret
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
web:
depends_on:
db:
condition: service_healthy
# Multi-tier application
services:
web:
networks: [frontend, backend]
cache:
networks: [backend]
db:
networks: [backend]
# Load environment from file with overrides
services:
app:
env_file: .env
environment:
LOG_LEVEL: debug # Override from .env
# Profiles for optional services
monitoring:
profiles: [monitoring]
debug:
profiles: [debug]
# Use compose watch for live reload (Compose 2.22+)
# docker compose watch
watch:
- path: ./src
action: sync
target: /app/src
- path: ./requirements.txt
action: rebuild
FAQ
What’s the difference between docker-compose.yml and compose.yaml?
Both are supported. Docker prefers compose.yaml (no dash). Both are equivalent; pick one convention and stick with it. Modern documentation uses compose.yaml.
How do I set environment variables in Docker Compose?
Use the environment key in a service, or load from a file with env_file: .env. Variables can be hardcoded, from the host shell (${VAR_NAME}), or from .env files. Use secrets for sensitive values.
How do I ensure one service starts before another?
Use depends_on: db: condition: service_healthy (requires a HEALTHCHECK). For just startup order without validation, use the short form depends_on: [db]; it only orders startup and does not wait for readiness.
How do I use Docker Compose override files?
Create docker-compose.override.yaml in the same directory. Compose automatically loads it and merges it with compose.yaml. Override specific services without editing the main file. Useful for local development vs. production.
What are Docker Compose profiles?
Profiles allow you to conditionally include services. Set profiles: [debug] on a service, then use docker compose --profile debug up to include it. Useful for optional debugging services or monitoring stacks.
How this guide was verified
Written by hand from the official documentation, every command re-checked, and reviewed a second time before publishing. Last reviewed on .
Official sources read for this article

