What are the key sections of a compose.yaml file?
Quick Answer
services defines each container to run, with its image or build instructions, ports, environment variables, and volumes. networks optionally defines custom networks beyond the default one Compose creates automatically. volumes declares named volumes referenced by one or more services, so their definitions (and driver options, if any) live in one place. Most Compose files only need to explicitly define services — networks and top-level volumes are often just referenced implicitly or with minimal extra configuration.
Detailed Answer
services — the core of every Compose file
services:
api:
image: myapi:1.0 # OR: build: ./api (build from a local Dockerfile instead)
ports:
- "3000:3000"
environment:
- NODE_ENV=production
volumes:
- api-logs:/app/logs
networks:
- backend
depends_on:
- db
restart: unless-stopped
Each entry under services corresponds to (roughly) one docker run invocation's worth of configuration — image/build source, ports, environment, volumes, network attachments, startup dependencies, and restart policy, all in one place, per service.
build vs. image — where the container's code comes from
services:
api:
build:
context: ./api
dockerfile: Dockerfile.prod
image: myapi:1.0 # optional: tag the resulting build with this name too
image alone pulls a pre-built image from a registry. build instead builds the image locally from a Dockerfile. The two are commonly used together during local development — building from source, then tagging it with a name. A production deployment, on the other hand, more often just references a pre-built image that CI already built and pushed.
networks (top-level) — custom networks beyond the default
services:
web:
networks:
- frontend
api:
networks:
- frontend
- backend
db:
networks:
- backend
networks:
frontend:
backend:
By default, Compose creates a single network shared by every service in the project. This top-level networks section, paired with a networks: list under each service, is only needed when you want the same kind of deliberate multi-network segmentation covered in the plain-Docker networking topic — for example, keeping db unreachable from web directly, only reachable via api.
volumes (top-level) — declaring named volumes
services:
db:
volumes:
- db-data:/var/lib/postgresql/data
volumes:
db-data:
driver: local # optional -- and can specify driver-specific options here too
A named volume referenced by a service (db-data above) should generally also be declared under the top-level volumes: section. This is where you would specify a non-default driver or driver options, if needed. A bare declaration — just db-data: with nothing else — is sufficient for the common case of a plain, Docker-managed local volume.
Environment variables and .env files
services:
api:
environment:
- DB_PASSWORD=${DB_PASSWORD} # substituted from a .env file or the shell environment
env_file:
- .env.api # load an entire file of variables at once
Covered in full in the dedicated environment-variables question — Compose supports both inline environment: entries (with variable substitution from a .env file) and loading an entire separate file via env_file:.
Why most real Compose files are dominated by the services section
For the common case of a single-network application with standard, Docker-managed volumes, the networks and volumes top-level sections often need little or no extra configuration beyond a bare declaration. The real substance of a Compose file — and what most of your time authoring one goes into — is correctly configuring each service's image or build, environment, ports, volumes, and dependencies within the services section.