Docker Compose File Structure Services Volumes and Networks

The compose.yaml file is the heart of Docker Compose. It describes every container your application needs, how they connect, and where they store data. This topic walks through every important section with clear diagrams so you can write confident Compose files from scratch.

The Top-Level Structure

compose.yaml skeleton:

services:          ← containers (required)
  service-name-1:
    ...
  service-name-2:
    ...

volumes:           ← named volumes (optional)
  volume-name-1:
  volume-name-2:

networks:          ← custom networks (optional)
  network-name-1:

Every Compose file has at least a services section. Volumes and networks are only needed when you want explicit control over them — otherwise Docker Compose creates sensible defaults automatically.

The services Section — Defining Each Container

Each entry under services becomes one container. The key is the service name, which also becomes the DNS hostname other containers use to reach it.

services:
  webapp:                          ← service name (also the hostname)
    image: my-flask-app:1.0        ← image to use (pull or local)
    build: ./app                   ← OR build from a Dockerfile here
    container_name: flask-web      ← custom container name (optional)
    restart: unless-stopped        ← restart policy
    ports:
      - "5000:5000"                ← HOST:CONTAINER port mapping
    environment:
      DATABASE_URL: postgres://...
      DEBUG: "false"
    env_file:
      - .env                       ← load vars from a file
    volumes:
      - ./logs:/app/logs           ← bind mount
      - appdata:/app/data          ← named volume
    networks:
      - app-net                    ← attach to custom network
    depends_on:
      db:
        condition: service_healthy ← wait for db to be ready
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:5000/health"]
      interval: 30s
      timeout: 10s
      retries: 3

image vs build — Two Ways to Get a Container Image

Option 1: Use an existing image
  services:
    db:
      image: postgres:15          ← pull from Docker Hub

Option 2: Build from a Dockerfile
  services:
    webapp:
      build: ./webapp             ← Dockerfile is in ./webapp folder

Option 3: Build with more control
  services:
    webapp:
      build:
        context: ./webapp         ← folder with files to send
        dockerfile: Dockerfile.prod  ← specific Dockerfile name
        args:
          APP_VERSION: "2.0"      ← build arguments

When you run docker compose up --build, it rebuilds images from Dockerfiles.
When you run docker compose up, it uses cached images.

restart Policies

restart: "no"             ← Never restart (default)
restart: always           ← Always restart, even on host reboot
restart: on-failure       ← Restart only if container exits with error
restart: unless-stopped   ← Restart always, except when manually stopped

Use unless-stopped for production services — they come back up after a server reboot but stay stopped if you intentionally stopped them with docker compose stop.

depends_on — Controlling Startup Order

services:
  webapp:
    image: my-app
    depends_on:
      db:
        condition: service_healthy   ← wait until db passes healthcheck
      redis:
        condition: service_started   ← wait until redis container starts (not ready)

  db:
    image: postgres:15
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      timeout: 5s
      retries: 5
Startup order with depends_on:

db starts → healthcheck passes → webapp starts
redis starts → webapp starts

Without depends_on:
All containers start at the same time → webapp crashes because db isn't ready

The volumes Section

services:
  db:
    image: postgres:15
    volumes:
      - pgdata:/var/lib/postgresql/data   ← reference named volume

  webapp:
    image: my-app
    volumes:
      - ./src:/app/src                    ← bind mount (no declaration needed)
      - appdata:/app/uploads              ← reference named volume

volumes:
  pgdata:          ← declares the named volume (Docker manages location)
  appdata:
    driver: local  ← explicitly set driver (local is default)

Named volumes declared at the top level persist across docker compose down. They are only deleted when you run docker compose down -v.

The networks Section

services:
  webapp:
    networks:
      - frontend
      - backend
  db:
    networks:
      - backend       ← db is only on backend, not reachable from frontend
  nginx:
    networks:
      - frontend      ← nginx only talks to webapp, not db directly

networks:
  frontend:           ← Docker creates this network
  backend:            ← Docker creates this one too
Network isolation diagram:

frontend network:        backend network:
  nginx ──────────────── webapp ──────────── db
  (public facing)        (app logic)         (data)

nginx cannot directly reach db → security by design

When you do not define networks, Compose creates one default network and all services join it. For production apps, explicit networks give you fine-grained security by isolating which services can talk to which.

A Complete Realistic Example

services:
  nginx:
    image: nginx:1.25
    ports:
      - "80:80"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
    depends_on:
      - webapp
    networks:
      - frontend

  webapp:
    build: ./app
    restart: unless-stopped
    env_file: .env
    depends_on:
      db:
        condition: service_healthy
    networks:
      - frontend
      - backend

  db:
    image: postgres:15
    restart: unless-stopped
    environment:
      POSTGRES_USER: appuser
      POSTGRES_PASSWORD: ${DB_PASSWORD}
      POSTGRES_DB: appdb
    volumes:
      - pgdata:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U appuser"]
      interval: 10s
      retries: 5
    networks:
      - backend

volumes:
  pgdata:

networks:
  frontend:
  backend:

Notice ${DB_PASSWORD} — Compose reads this from a .env file in the same directory automatically, keeping secrets out of the Compose file.

Leave a Comment