Skip to content

The compose.yaml File

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.

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:24-alpine
db:
image: postgres:18-alpine

Here api and db are two services. Compose creates one container for each.

Use image to pull a pre-built image from a registry:

services:
web:
image: nginx:alpine

Use 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:dev

ports 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"

Pass environment variables into the container either as a list or a mapping:

# list form
environment:
- NODE_ENV=production
- PORT=3000
# mapping form
environment:
NODE_ENV: production
PORT: "3000"

Both forms are equivalent. The list form mirrors shell export syntax and is most common.

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.js

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.

services:
app:
build: .
ports:
- "3000:3000"
environment:
- NODE_ENV=production
- DATABASE_URL=postgres://db:5432/mydb
command: ["node", "src/server.js"]
db:
image: postgres:18-alpine
environment:
- POSTGRES_PASSWORD=secret

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
OptionBenefitCost
Docker ComposeOne compose.yaml file, runs locally right away, fits a dev loop wellActually only runs on a single host — no self-healing across nodes
KubernetesScales and self-heals across many nodes in a clusterRequires learning manifests, controllers, and much higher operational overhead
  • Forgetting that services is the only required key, then trying to place service-level config in the wrong spot (like putting image: outside a service block).
  • Adding version: "3.9" back into a new compose.yaml because it was copied from an old tutorial, when current tooling no longer reads that key.
  • Forgetting to quote a port mapping (writing ports: - 8080:80 instead of "8080:80"), which can make the YAML parser misread the colon in some cases.

💡 Real-world example

Many dev teams use the same compose.yaml as the starting point for both local development and the production image — getting the services structure right in the compose file first, then taking that same image to deploy on Kubernetes later.

Which top-level key is required in every compose.yaml?
What is the correct way to map host port 9000 to container port 80 in compose.yaml?
Why should you omit the version: key from new compose.yaml files?
What does the build: . key do in a service definition?