Skip to main content
Navigation
HomeTechnical ReferenceJournalGitHubGitHub
Sidebar — toggle document categories via the logo
Categories

Docker Compose Reference

Overview​

Docker Compose defines and runs multi-container applications. A single YAML file (docker-compose.yml) describes services, networks, and volumes, letting you spin up a full stack with one command.

Quick reference​

These are the everyday Compose commands you'll reach for most. up builds and starts services, down tears the stack down, and ps, logs, and exec help you interact with a running stack. Add -d to run detached and --build to force a rebuild before starting.

docker compose up # start all services (foreground)
docker compose up -d # start in detached mode (background)
docker compose up -d --build # rebuild images before starting
docker compose down # stop and remove containers, networks
docker compose down -v # also remove named volumes (destructive)
docker compose down --rmi all # also remove the images

docker compose start # start existing stopped services
docker compose stop # stop running services gracefully
docker compose restart # restart all services
docker compose restart webapp # restart a specific service
docker compose pause # freeze services in place
docker compose unpause # resume paused services

docker compose ps # list services and their status
docker compose ps -a # include stopped services
docker compose top webapp # show processes running in a service

docker compose logs # logs from all services
docker compose logs -f # follow new log lines in real time
docker compose logs webapp db # logs from specific services only
docker compose logs --tail=50 webapp # last 50 lines for one service

docker compose exec webapp sh # run a command in a running service
docker compose exec -T webapp ls # non-interactive (for CI scripts)
docker compose run webapp sh # start a fresh container for a one-off command
docker compose run --rm webapp npm test # one-off command, cleaned up on exit

docker compose build # build or rebuild service images
docker compose build --no-cache # force a full rebuild
docker compose build webapp # build a specific service

docker compose pull # pull the latest images from the registry
docker compose push # push built service images to the registry

docker compose config # validate and view the resolved configuration
docker compose config --services # list just the service names
docker compose config --volumes # list just the volume names

Compose file reference​

Top-level keys​

A Compose file is organized around a handful of top-level keys. services is the only required one — it defines your containers — while networks, volumes, configs, and secrets declare shared resources those services reference. version is optional with modern Compose and is mainly kept for compatibility.

version: "3.9" # Compose file version (optional in modern compose)
name: myapp # project name (prefixes container, network, and volume names)
services: # container definitions (required)
networks: # custom network definitions
volumes: # named volume definitions
configs: # config objects (swarm)
secrets: # secret objects (swarm)

Service definition​

A service is the Compose analog of a docker run invocation: it names an image or build context plus the runtime settings — ports, environment, volumes, healthchecks, and more. This example shows a typical webapp service with every commonly used key annotated.

services:
webapp:
image: nginx:alpine # image from registry
build: # build from Dockerfile
context: ./www # build context path
dockerfile: Dockerfile.prod # Dockerfile name
args: # build arguments
NODE_ENV: production
target: builder # multi-stage target
container_name: my-webapp # custom container name
hostname: webapp # container hostname
restart: unless-stopped # restart policy
ports: # port mappings
- "8080:80"
- "443:443"
expose: # expose ports to linked services (not host)
- "3000"
environment: # environment variables
NODE_ENV: production
DB_HOST: db
env_file: # load from env file
- ./config/webapp.env
volumes: # mount volumes
- ./www:/usr/share/nginx/html:ro
- app_data:/var/lib/data
networks: # attach to networks
- frontend
- backend
depends_on: # dependency ordering
db:
condition: service_healthy
healthcheck: # health check
test: ["CMD", "curl", "-f", "http://localhost/health"]
interval: 30s
timeout: 5s
retries: 3
command: ["nginx", "-g", "daemon off;"] # override default command
entrypoint: ["/custom-entrypoint.sh"] # override entrypoint
user: "1000:1000" # run as user
working_dir: /app # working directory
profiles: # conditional activation
- debug
deploy: # swarm deployment config
replicas: 3
resources:
limits:
cpus: "0.5"
memory: 512M
init: true # use init process
stop_grace_period: 30s # grace period before SIGKILL
extra_hosts: # add hosts entries
- "api.internal:10.0.0.5"
logging: # logging driver
driver: json-file
options:
max-size: "10m"
max-file: "3"

Common service patterns​

A typical stack couples a web frontend, an API backend, a database, and a cache. This example shows the standard wiring: the web service reaches the API over the internal network, the API waits for a healthy database before starting, and data lives in named volumes that outlive the containers.

# Web frontend
services:
web:
build: ./www
ports:
- "80:3000"
environment:
- API_URL=http://api:4000
depends_on:
- api

# API backend
api:
build: ./api
expose:
- "4000"
environment:
- DATABASE_URL=postgresql://user:pass@db:5432/mydb
depends_on:
db:
condition: service_healthy

# PostgreSQL database
db:
image: postgres:16-alpine
volumes:
- pgdata:/var/lib/postgresql/data
environment:
POSTGRES_USER: user
POSTGRES_PASSWORD: pass
POSTGRES_DB: mydb
healthcheck:
test: ["CMD-SHELL", "pg_isready -U user -d mydb"]
interval: 5s
timeout: 5s
retries: 5

# Redis cache
cache:
image: redis:7-alpine
volumes:
- redisdata:/data

volumes:
pgdata:
redisdata:

Networks​

Declare custom networks here so services can reach each other by container name. Every network uses the bridge driver by default; override driver or tune IPAM (subnets) only when you need explicit addressing or cross-host connectivity.

networks:
frontend: # default: bridge driver
backend:
driver: bridge
driver_opts:
com.docker.network.bridge.name: br-backend
ipam:
config:
- subnet: 172.28.0.0/16

Volumes​

Named volumes declared at the top level persist data independently of any container. Setting external: true tells Compose that the volume already exists — on the host or managed by another stack — so it will use it instead of creating a new one.

volumes:
pgdata:
driver: local
driver_opts:
type: none
o: bind
device: /mnt/storage/pgdata
redisdata:
external: true # volume is managed outside of compose

Profiles (conditional services)​

Profiles mark services as optional: they are skipped by a normal docker compose up and are only started when their profile is enabled with --profile. This is ideal for debug tooling, one-off jobs, or dev-only services without maintaining a second compose file.

services:
app:
build: .
debug-tools:
image: nicolaka/netshoot
profiles: # only started with --profile debug
- debug
command: sleep infinity

Start the stack with the profile enabled to include debug-tools:

docker compose up -d # only starts app
docker compose --profile debug up -d # also starts debug-tools

Extends and fragments (YAML anchors)​

YAML anchors (&name / *name) and extension fields (x-...) let you define shared configuration once and merge it into multiple services with <<:. This is the modern replacement for the deprecated extends key — keep repetitive settings like restart policies and logging options in a single place.

x-common: &common
restart: unless-stopped
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"

services:
webapp:
<<: *common
image: nginx:alpine

api:
<<: *common
image: myapp:latest

Using multiple compose files​

Compose merges several files together, with later files overriding earlier ones. Pass them explicitly with -f, and note that docker-compose.override.yml is picked up automatically when present — reserve it for local development and use a named file such as docker-compose.prod.yml for environments.

# Base config + override
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d

# docker-compose.override.yml is applied automatically
# docker-compose.prod.yml only when explicitly specified

The base file holds the shared config; the prod file overrides ports, restart policy, and environment for production:

# docker-compose.yml
services:
web:
build: .
ports:
- "80:3000"

# docker-compose.prod.yml
services:
web:
restart: always
ports:
- "443:3000"
environment:
- NODE_ENV=production

Environment variable interpolation​

Compose supports ${VARIABLE} substitution from shell environment or .env files:

services:
web:
image: ${DOCKER_REGISTRY:-docker.io}/myapp:${APP_VERSION:-latest}
ports:
- "${HOST_PORT:-8080}:3000"

Default values: ${VAR:-default} (empty/unset → default), ${VAR-default} (only unset → default).

Error if missing: ${VAR:?error message}.

# .env file in the same directory as docker-compose.yml
DOCKER_REGISTRY=my.registry.com
APP_VERSION=2.1.0
HOST_PORT=9090

Production considerations​

Hardening a stack for production is mostly about defaults: restart policies, resource limits, healthchecks, log rotation, and dropping capabilities the app does not need. This example bundles the most important production settings into one service definition — adjust the values to match your workload.

services:
api:
restart: unless-stopped # or 'always' for critical services
init: true # handle signals properly
read_only: true # root FS read-only (except volumes)
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
cap_add:
- NET_BIND_SERVICE
deploy:
resources:
limits:
cpus: "1.0"
memory: 512M
reservations:
cpus: "0.5"
memory: 256M
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval: 15s
timeout: 5s
retries: 3
start_period: 30s
logging:
driver: json-file
options:
max-size: "50m"
max-file: "5"

See also​