From unit 4, booting the two-container app by hand took roughly four or five long commands, in the right order, every single time.
The sequence was a network create, a volume create, a db run with volume and network, and an app run with network and ports, each carrying flags that must be remembered exactly.
| Step | Command |
|---|---|
| 1 | docker network create mynet |
| 2 | docker volume create dbdata |
| 3 | docker run -d --name db --network mynet -v dbdata:... |
| 4 | docker run -d --name app --network mynet -p 8080:8000 ... |
Missing a flag or running them out of order breaks the app. That repetition is exactly what docker compose eliminates.
docker compose
Compose lets you describe your whole multi-container setup in one file, docker-compose.yml, and boot it with one command:
docker compose up -d
The file is written in YAML, a config format where structure comes from indentation and most lines are key: value. Two spaces per level, no tabs. You will read YAML constantly from here on, since GitHub Actions in unit 7 uses it too.
Compose reads the file, then creates the network, the volumes, and every container, in the right order, with the right flags. Delete everything just as easily with docker compose down. The file lives in your repo, so the entire runnable app setup is version-controlled with git, like everything else.
The file, decoded line by line
services:
app:
build: .
ports:
- "8080:8000"
environment:
DATABASE_URL: postgres://shop:secret@db:5432/shop
depends_on:
- db
db:
image: postgres:16
volumes:
- dbdata:/var/lib/postgresql/data
volumes:
dbdata:| Key | Meaning | Hand-run equivalent |
|---|---|---|
services: | the containers to run | one docker run each |
build: . | build this image from the local Dockerfile | docker build . |
ports: | published port mapping | -p 8080:8000 |
environment: | env vars handed to the container | -e |
depends_on: | start order | ordering by hand |
image: postgres:16 | use a prebuilt image | docker run postgres:16 |
volumes: | mount a named volume | -v dbdata:... |
Each key under services: is one service, and its name becomes the container's network hostname, exactly like --name in lesson 4-2. The top-level volumes: block declares the named volume that the service then mounts.
Compose also creates a private network for these services automatically, so no docker network create is needed.
Why the db hostname resolves
The app's DATABASE_URL can point at host db because compose puts all services on a shared network where each service name is a DNS hostname, exactly as in lesson 4-2.
Compose auto-creates a network and connects every service to it, and service names become hostnames just like container names on a user-defined network.
| Compose service name | Hostname other services use |
|---|---|
db | db |
database | database |
Renaming the service to database forces the URL to change to match, which is a real source of confusion in reviews. The name in the compose file is not decoration, it is the DNS record.
build against image
The app service says build: . while db says image: postgres:16, and the difference is where the image comes from.
build: makes compose build the image from your local Dockerfile, running the unit 3 docker build for you. image: pulls a ready-made image from a registry.
| Key | Source of the image | Typical use |
|---|---|---|
build: . | your Dockerfile | your own code |
image: postgres:16 | a registry | standard software |
Your own code needs building, while standard software such as Postgres ships as a prebuilt image you simply pull. Most real compose files mix both, exactly like this one.
The filename compose looks for
The conventional filename is docker-compose.yml, and the newer compose.yaml is also accepted.
It is YAML, so it ends in .yml or .yaml, and the classic name starts with docker-compose.
| Filename | Accepted |
|---|---|
docker-compose.yml | yes, the classic |
compose.yaml | yes, the newer form |
docker-compose.yaml | yes |
Compose reads it from the current directory when docker compose up runs, so the whole app definition travels inside the repo. Anyone who clones the project gets the deployment topology along with the code.