Compose File Format
This is a detailed reference for the Docker Compose file format. The Compose file defines services, networks, volumes, secrets, and configs for a Docker application.
File Discovery
Docker Compose looks for files in the following order:
compose.yaml(preferred)compose.ymldocker-compose.yamldocker-compose.yml
Override files are merged automatically:
docker-compose.yml+docker-compose.override.yml- Or specify explicitly:
docker compose -f base.yml -f override.yml up
Top-Level Elements
yaml
# Optional: Name of the project
name: my-application
# Service definitions (required)
services:
# ...
# Network definitions (optional)
networks:
# ...
# Volume definitions (optional)
volumes:
# ...
# Secret definitions (optional)
secrets:
# ...
# Config definitions (optional)
configs:
# ...Services Reference
Complete Service Options
yaml
services:
my-service:
# ─── Image / Build ─────────────────────────
image: nginx:1.25 # Pre-built image
build: # Build from source
context: . # Build context path
dockerfile: Dockerfile # Dockerfile path
args: # Build arguments
NODE_ENV: production
target: production # Multi-stage target
cache_from: # Cache sources
- type=registry,ref=cache:latest
cache_to: # Cache destinations
- type=registry,ref=cache:latest
platforms: # Target platforms
- linux/amd64
- linux/arm64
labels: # Build labels
com.example.version: "1.0"
no_cache: false # Disable cache
pull: false # Always pull base
# ─── Container Config ──────────────────────
container_name: my-container # Fixed name
hostname: my-host # Hostname
domainname: example.com # Domain
command: ["node", "server.js"] # Override CMD
entrypoint: ["docker-entrypoint.sh"] # Override ENTRYPOINT
working_dir: /app # Working directory
user: "1000:1000" # User:Group
init: true # Use init process
stdin_open: true # Keep STDIN open
tty: true # Allocate TTY
platform: linux/amd64 # Target platform
pull_policy: always # Image pull policy
# pull_policy: always | missing | never | build
# ─── Networking ────────────────────────────
ports:
- "80:80" # host:container
- "127.0.0.1:443:443" # Localhost only
- target: 3000 # Long syntax
published: "3000"
protocol: tcp
mode: host
expose:
- "3000" # Expose to linked services
networks:
frontend:
aliases:
- web
ipv4_address: 172.28.0.10
backend:
network_mode: host # host | bridge | none
dns:
- 8.8.8.8
- 8.8.4.4
dns_search:
- example.com
extra_hosts:
- "host.docker.internal:host-gateway"
- "myhost:192.168.1.100"
# ─── Storage ───────────────────────────────
volumes:
- data:/app/data # Named volume
- ./src:/app/src # Bind mount
- ./config:/app/config:ro # Read-only
- type: volume # Long syntax
source: data
target: /app/data
read_only: false
- type: bind
source: ./logs
target: /app/logs
- type: tmpfs
target: /tmp
tmpfs:
size: 100000000
tmpfs:
- /tmp
- /var/run
# ─── Environment ──────────────────────────
environment:
NODE_ENV: production
DB_HOST: db
env_file:
- .env
- path: .env.local
required: false
# ─── Dependencies ──────────────────────────
depends_on:
db:
condition: service_healthy
restart: true
redis:
condition: service_started
migrations:
condition: service_completed_successfully
# ─── Health Check ──────────────────────────
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
start_interval: 5s
disable: false
# ─── Restart Policy ────────────────────────
restart: unless-stopped
# restart: "no" | always | on-failure | unless-stopped
# ─── Resources ─────────────────────────────
deploy:
resources:
limits:
cpus: '2.0'
memory: 1G
pids: 200
reservations:
cpus: '0.5'
memory: 256M
replicas: 3
update_config:
parallelism: 1
delay: 10s
order: start-first
rollback_config:
parallelism: 1
delay: 5s
restart_policy:
condition: on-failure
delay: 5s
max_attempts: 3
window: 120s
placement:
constraints:
- node.role == worker
# ─── Logging ───────────────────────────────
logging:
driver: json-file
options:
max-size: "10m"
max-file: "5"
compress: "true"
# ─── Security ──────────────────────────────
security_opt:
- no-new-privileges:true
- seccomp:custom.json
cap_drop:
- ALL
cap_add:
- NET_BIND_SERVICE
read_only: true
privileged: false
# ─── Resource Limits ───────────────────────
ulimits:
nproc: 65535
nofile:
soft: 20000
hard: 40000
mem_limit: 512m
mem_reservation: 256m
cpus: 1.5
cpu_shares: 512
pids_limit: 100
shm_size: '128m'
# ─── Secrets & Configs ─────────────────────
secrets:
- db_password
- source: api_key
target: /run/secrets/api_key
uid: '103'
gid: '103'
mode: 0440
configs:
- source: nginx_conf
target: /etc/nginx/nginx.conf
# ─── Labels ────────────────────────────────
labels:
com.example.project: "myapp"
com.example.environment: "production"
# ─── Lifecycle ─────────────────────────────
stop_grace_period: 30s
stop_signal: SIGTERM
# ─── Profiles ──────────────────────────────
profiles:
- debug
- development
# ─── Extensions ────────────────────────────
x-custom-label: "value"Networks Reference
yaml
networks:
# Simple network (Docker manages settings)
default:
# Bridge network with configuration
frontend:
driver: bridge
driver_opts:
com.docker.network.bridge.name: frontend-br
ipam:
driver: default
config:
- subnet: 172.28.0.0/16
ip_range: 172.28.5.0/24
gateway: 172.28.0.1
# Internal network (no external access)
backend:
driver: bridge
internal: true
# External network (pre-existing)
existing-network:
external: true
name: my-existing-network
# Network with labels
labeled:
labels:
com.example.project: "myapp"
# Overlay network (Swarm mode)
swarm-net:
driver: overlay
attachable: trueVolumes Reference
yaml
volumes:
# Simple volume
data:
# Volume with driver
db-data:
driver: local
# Volume with driver options
nfs-data:
driver: local
driver_opts:
type: nfs
o: "addr=192.168.1.100,nolock,soft,rw"
device: ":/exports/data"
# Bind mount as volume
host-data:
driver: local
driver_opts:
type: none
o: bind
device: /srv/data
# External volume
shared:
external: true
name: my-shared-volume
# Volume with labels
logs:
labels:
com.example.description: "Log storage"Secrets Reference
yaml
secrets:
# File-based secret
db_password:
file: ./secrets/db_password.txt
# Environment variable secret
api_key:
environment: API_KEY
# External secret (Swarm mode)
external_secret:
external: true
name: my-external-secretConfigs Reference
yaml
configs:
# File-based config
nginx_conf:
file: ./nginx/nginx.conf
# Content-based config
app_config:
content: |
server.port=8080
server.host=0.0.0.0
# External config
external_config:
external: true
name: my-external-configVariable Substitution
yaml
services:
web:
image: nginx:${NGINX_VERSION:-1.25}
ports:
- "${HOST_PORT:-80}:80"
environment:
- DB_HOST=${DB_HOST:?DB_HOST must be set}| Syntax | Behavior |
|---|---|
${VAR} | Value of VAR, empty if unset |
${VAR:-default} | Value of VAR, or default if unset or empty |
${VAR-default} | Value of VAR, or default if unset |
${VAR:?error} | Value of VAR, or error if unset or empty |
${VAR?error} | Value of VAR, or error if unset |
${VAR:+replacement} | replacement if VAR is set and non-empty, else empty |
${VAR+replacement} | replacement if VAR is set (even if empty), else empty |
Extensions (x- prefix)
Custom fields for reuse via YAML anchors:
yaml
x-common-env: &common-env
LOG_LEVEL: info
TZ: UTC
x-common-deploy: &common-deploy
resources:
limits:
cpus: '0.5'
memory: 256M
reservations:
cpus: '0.25'
memory: 128M
x-common-healthcheck: &common-healthcheck
interval: 30s
timeout: 10s
retries: 3
start_period: 30s
services:
api:
image: my-api:latest
environment:
<<: *common-env
API_PORT: "3000"
deploy:
<<: *common-deploy
healthcheck:
<<: *common-healthcheck
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
worker:
image: my-worker:latest
environment:
<<: *common-env
WORKER_CONCURRENCY: "4"
deploy:
<<: *common-deploy
healthcheck:
<<: *common-healthcheck
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]Profiles
Profiles allow selective service startup:
yaml
services:
web:
image: nginx:latest
# No profile: always starts
api:
image: my-api:latest
# No profile: always starts
debug:
image: nicolaka/netshoot
profiles:
- debug
test:
image: my-test:latest
profiles:
- test
monitoring:
image: prom/prometheus
profiles:
- monitoring
- productionbash
# Start only default services
docker compose up -d
# Start with debug profile
docker compose --profile debug up -d
# Start with multiple profiles
docker compose --profile debug --profile monitoring up -dMerge and Override
bash
# Automatic merge (base + override)
docker compose up # Merges docker-compose.yml + docker-compose.override.yml
# Explicit merge
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d
# Multiple overrides (applied in order)
docker compose \
-f docker-compose.yml \
-f docker-compose.prod.yml \
-f docker-compose.monitoring.yml \
up -dMerge Rules
| Field | Merge Behavior |
|---|---|
| Single values (image, command) | Override replaces |
| Maps (environment, labels) | Deep merge |
| Lists (ports, volumes) | Append |
| depends_on | Deep merge |
| deploy | Deep merge |
Validation
bash
# Validate and display resolved config
docker compose config
# Output as JSON
docker compose config --format json
# Validate specific files
docker compose -f docker-compose.yml -f docker-compose.prod.yml config
# Show only services
docker compose config --services
# Show only volumes
docker compose config --volumesNext Steps
- Dockerfile Reference — Complete Dockerfile instruction reference
- CLI Reference — Docker CLI command reference
- Compose File Reference — Practical Compose file guide
- Docker Compose Quick Start — Getting started with Compose
- Compose in Production — Production deployment guide