The compose.yaml File
compose.yaml at a glance
Section titled “compose.yaml at a glance”Every Compose file has one required top-level key: services. Everything else — networks, volumes, configs — is optional.
services: service-name: image: some-image:tag build: . ports: - "hostPort:containerPort" environment: - KEY=value command: ["executable", "arg"]Each key under services is a service name you choose. Compose starts one container per service by default.
The services key
Section titled “The services key”services is a map. Each entry is a named service. The name becomes both the container identifier and the DNS hostname other services can use to reach it on the internal network.
services: api: image: node:22-alpine db: image: postgres:17-alpineHere api and db are two services. Compose creates one container for each.
image vs build
Section titled “image vs build”Use image to pull a pre-built image from a registry:
services: web: image: nginx:alpineUse build to build an image from a local Dockerfile at build time:
services: app: build: .You can combine them — build says where to find the Dockerfile, and image names the resulting image:
services: app: build: . image: myapp:devports maps a host port to a container port in "host:container" format. Always quote the mapping to avoid YAML interpreting the colon:
services: web: image: nginx:alpine ports: - "8080:80" - "8443:443"environment
Section titled “environment”Pass environment variables into the container either as a list or a mapping:
# list formenvironment: - NODE_ENV=production - PORT=3000
# mapping formenvironment: NODE_ENV: production PORT: "3000"Both forms are equivalent. The list form mirrors shell export syntax and is most common.
command
Section titled “command”Override the default CMD from the image:
services: worker: image: myapp:latest command: ["node", "worker.js"]Pass a plain string for a shell-interpreted command:
services: worker: image: myapp:latest command: node worker.jsNo version key needed
Section titled “No version key needed”Older tutorials show a version: "3.9" key at the top of the file. This is obsolete. The Compose Specification no longer requires or recommends the version key. Modern tooling ignores it. Omit it from all new files.
A complete example
Section titled “A complete example”services: app: build: . ports: - "3000:3000" environment: - NODE_ENV=production - DATABASE_URL=postgres://db:5432/mydb command: ["node", "src/server.js"] db: image: postgres:17-alpine environment: - POSTGRES_PASSWORD=secretHands-on practice
Section titled “Hands-on practice”The snippet below writes a complete compose.yaml and starts it so you can inspect each field in action.
# Write a compose.yaml with two services
cat > compose.yaml <<'EOF'
services:
web:
image: nginx:alpine
ports:
- "8080:80"
environment:
- NGINX_HOST=localhost
command: ["nginx", "-g", "daemon off;"]
echo:
image: alpine
command: ["sh", "-c", "echo 'compose.yaml works!' && sleep 30"]
EOF
# Start both services
docker compose up -d
# Show running containers
docker compose ps
# Read echo output
docker compose logs echo
# Clean up
docker compose down