Services and containers come and go. Networks and volumes outlive them, so Compose defines them in their own top-level sections and services refer to them by key.
services:
web:
networks: [backend]
db:
networks: [backend]
volumes:
- db-data:/var/lib/postgresql/data
networks: # declared once, at the top level
backend:
volumes:
db-data: signup_backend (bridge)
┌──────────────┴──────────────┐
signup-web-1 signup-db-1
:5000 ◄─ host 8000 /var/lib/postgresql/data
│
signup_db-data (volume)Networks
Without networks:, every service joins <project>_default. That is enough for most stacks. Declare your own networks to give them a clear name or to split tiers:
services:
proxy:
networks: [frontend]
web:
networks: [frontend, backend]
db:
networks: [backend] # proxy cannot reach db
networks:
frontend:
backend:| Fact | Detail |
|---|---|
| Real name | <project>_<key>, e.g. signup_backend |
| DNS | service names resolve on every network the service joins |
| Default network | created only if some service has no networks: |
| Removed by | docker compose down |
Volumes
A volume entry under a service is SOURCE:TARGET[:ro]. The source decides the type:
| Entry | Type | Lives in |
|---|---|---|
db-data:/var/lib/postgresql/data | named volume | signup_db-data, managed by Docker |
./site:/usr/share/nginx/html:ro | bind mount | the host directory ~/signup/site |
/var/lib/postgresql/data (target only) | anonymous volume | a volume with a random name |
Named volumes must also appear under the top-level volumes: key. An empty value (db-data:) means the default local driver.
service "db" refers to undefined volume db-data: invalid compose projectWhy the database needs a named volume
The postgres image declares /var/lib/postgresql/data as a volume. Without a volumes: entry, each new container gets a new anonymous volume:
up signup-db-1 ── anonymous volume A (rows written here)
down container removed; A left behind, unused
up signup-db-1 ── anonymous volume B (empty: initdb runs again)With db-data:/var/lib/postgresql/data, every new db container mounts the same signup_db-data. docker compose down removes containers and networks but keeps named volumes. On the next start, Postgres logs:
PostgreSQL Database directory appears to contain a database; Skipping initializationThe stack so far
The healthcheck and depends_on lines make web wait for Postgres; lesson 7 explains them.
services:
web:
build: .
ports:
- "8000:5000"
environment:
DB_PASSWORD: workshop-pass
depends_on:
db:
condition: service_healthy
networks:
- backend
db:
image: postgres:16
environment:
POSTGRES_PASSWORD: workshop-pass
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 2s
timeout: 3s
retries: 10
networks:
- backend
volumes:
- db-data:/var/lib/postgresql/data
networks:
backend:
volumes:
db-data: