How to Use YAML in Docker Compose

If Docker Compose was your first real encounter with YAML, you're not alone — docker-compose.yml is probably the single most common YAML file developers write by hand. Let's go through a small but realistic one.

version: "3.9"
services:
  web:
    build: .
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=production
    depends_on:
      - db
  db:
    image: postgres:16
    environment:
      POSTGRES_PASSWORD: secret
    volumes:
      - db_data:/var/lib/postgresql/data
volumes:
  db_data:

Reading it top to bottom

services is a map where each key — web, db — names one container. Everything indented under web describes just that container: build: . means build an image from the Dockerfile in the current folder, and ports is a list mapping host port to container port.

environment shows up twice, written two different valid ways — as a list of KEY=value strings under web, and as a map of KEY: value under db. Both are accepted by Compose; pick whichever you find more readable and stay consistent within a file.

depends_on is just a list of other service names that should start first. volumes at the bottom, outside of services, declares a named volume that db references — notice it's indented at the top level, a sibling of services, not nested inside it.

The mistake that trips people up most

Indentation depth is everything here. If db accidentally ends up indented under web instead of being its own sibling under services, Compose will read it as a setting on the web service rather than a second container — and you'll get a confusing error, or worse, it'll silently do the wrong thing.

Before running docker compose up on a file you've hand-edited, it's worth pasting it into the validator just to confirm the structure is what you think it is. It won't check Docker-specific rules, but it will catch the whitespace mistakes that cause most of the confusing failures.

← Format your YAML now