Anatomy of a docker-compose.yml
The Compose file is a YAML document with a small set of top-level keys. Understanding each key's purpose is the foundation for everything else: version (the Compose file format version, determining available features), services (required — the containers you want to run), volumes (optional — named volumes for persistent storage), and networks (optional — custom networks beyond the default).
The version field maps to the Docker Engine API version and determines which features are available. Version 3.8 is a safe, widely-supported choice. As of Compose Specification (2021), the version field is technically optional and deprecated in favor of a unified spec, but you will still see it in virtually every real-world file.
Defining Services
The heart of Compose is the services block. Each entry corresponds to one container. A service can either build an image from a local Dockerfile or pull a pre-built image from a registry.
A realistic three-tier application defines three services: a web service that builds from the local Dockerfile, maps a host port to the container's port, sets environment variables (including a DATABASE_URL pointing at db:5432 and a REDIS_URL pointing at redis:6379 — using the service names as hostnames), lists db and redis under depends_on, and sets a restart: on-failure policy; a db service that pulls the postgres:13 image, sets its password and database name via environment variables, and mounts a named volume at Postgres's data directory to persist it across restarts; and a redis service that just pulls the redis:7-alpine image with no further configuration. The named volume used by db is declared once at the top level so Docker manages its storage.
Key fields explained:
| Field | Purpose |
|---|---|
build |
Path to a directory containing a Dockerfile |
image |
Pull a pre-built image from Docker Hub or a private registry |
ports |
Expose container ports to the host (host:container) |
environment |
Set environment variables inside the container |
depends_on |
Start this service only after the listed services have started |
volumes |
Mount host paths or named volumes into the container |
restart |
Restart policy: no, always, on-failure, unless-stopped |
The depends_on Caveat
depends_on controls startup order, not readiness. It tells Compose to start the db container before web, but it does not wait for Postgres to finish initializing and accept connections. Your application code must handle connection retries, or you can use a health-check-aware depends_on configuration: giving web's depends_on entry for db a condition: service_healthy, and giving db itself a healthcheck (e.g. running pg_isready on an interval with a retry count) that Compose waits on before starting web.
This is the production-grade pattern. Without it, your app container may crash on startup because it tries to connect to Postgres before Postgres is ready.