Compose applications
Describe services with Compose
Replace a collection of long docker run commands with one versioned description of the application services.
By now running the Notes API takes a network, a volume, a database container with environment variables, and the API container with a port mapping. That’s four or five long docker run commands, in the right order. Nobody types those every day.
Docker Compose replaces them with one file, compose.yaml, that describes the containers, networks, and volumes of your application. You commit it with the code. Anyone on the team runs docker compose up and gets the same setup.
For a longer walkthrough with a Node plus Postgres example, see Introduction to Docker Compose.
Services
In Compose, a service is a container configuration you can start again and again. Here’s the Notes API and its database:
services:
api:
build: .
ports:
- "8080:3000"
database:
image: postgres:17-alpine
volumes:
- database-data:/var/lib/postgresql/data
volumes:
database-data:
api is built from the Dockerfile in the current directory and publishes 8080 to 3000, the same as our -p flag. database uses the official image and mounts a named volume at PostgreSQL’s data directory. The top-level volumes: block declares that volume so Compose creates it.
Two things happen without you asking. Compose creates a network for the project and attaches both services to it. And service names work as hostnames on that network, so the API reaches the database at database:5432. The services stay separate containers, though. Compose doesn’t merge them into one.
The file describes the desired state
Think of compose.yaml as “how things should be”. docker compose up compares it with what’s running and does whatever is needed: create what’s missing, recreate what changed, leave the rest alone. Edit the file, run up again, and only the affected containers move.
Compose also prefixes every container, network, and volume with a project name, by default the folder name. So the database container is notes-api-database-1 and the volume is notes-api_database-data. That’s why the same file can run twice as two separate projects, with -p to pick a different name.
Read the config before you trust it
Before any risky change, run:
docker compose config
It prints the fully resolved configuration: environment variables interpolated, multiple files merged, defaults filled in. Nothing starts. I read this output before deploying anything with Compose, looking for a host port I didn’t mean to publish, a bind mount pointing at the wrong folder, or a variable that resolved to an empty string.
Compose describes how services run. It doesn’t run your database migrations and it doesn’t make secret handling safe. Those are still your job.
Try this: create the file and run docker compose config. Compare the output with what you wrote, and notice the project name and the network Compose added.
Lesson completed