Docker Compose
Objectives
By the end of this lesson, you should be able to:
- Explain what Docker Compose adds on top of individual
docker runcommands - Write a Compose file running an API together with a database and a cache
- Use named volumes to persist database data across container restarts
💡 Why this matters: A real application in this track isn’t just one container, it’s an API, a PostgreSQL database (Course 2), and, starting in Module 7 of this course, a Redis cache, all needing to run together, and be able to reach each other, Docker Compose is what coordinates that with one command instead of several.
⚠️ A note on verification: Docker can’t run inside this course’s own sandboxed tooling. The Compose file and commands below reflect Docker Compose’s stable, current, documented behavior. Run this yourself, on your own machine, with Docker installed.
A Compose File
services:
api:
build: .
ports:
- "3000:3000"
environment:
- NODE_ENV=development
- DATABASE_URL=postgresql://postgres:postgres@db:5432/notes_dev
- REDIS_URL=redis://cache:6379
depends_on:
- db
- cache
db:
image: postgres:16
environment:
- POSTGRES_PASSWORD=postgres
- POSTGRES_DB=notes_dev
volumes:
- db-data:/var/lib/postgresql/data
ports:
- "5432:5432"
cache:
image: redis:7
ports:
- "6379:6379"
volumes:
db-data:
Three services, api (built from the local Dockerfile, Lesson 2), db (a stock PostgreSQL image, no custom build needed), and cache (a stock Redis image), each with its own configuration, all defined in one file.
Service Names as Hostnames
DATABASE_URL=postgresql://postgres:postgres@db:5432/notes_dev, db, not localhost, this is the key mechanic Compose provides, every service can reach every other service by its name in the Compose file, on Compose’s own internal network, api’s code doesn’t need to know or care about container IP addresses, db and cache are just hostnames that resolve correctly, automatically.
Named Volumes
db-data:/var/lib/postgresql/data mounts a named volume, managed by Docker, at PostgreSQL’s data directory inside the container. Without it, a database’s data would live only inside the container’s own filesystem, gone the moment that container is removed, a named volume persists independently, surviving a container restart, or even a full rebuild of the db service, exactly what any real database needs.
Running It
docker compose up
[+] Running 3/3
✔ Container myapp-db-1 Started
✔ Container myapp-cache-1 Started
✔ Container myapp-api-1 Started
One command, all three services started together, in dependency order (depends_on ensures db and cache start before api), connected on the same internal network, docker compose down stops and removes them, docker compose down -v additionally removes the named volumes, actually deleting the database’s data, useful for a genuinely clean slate, dangerous to run by accident against anything that matters.
Why This Matters for Local Development
Before Compose, running this stack locally meant manually installing PostgreSQL and Redis, or hand-running several separate docker run commands with the networking flags to let them reach each other, matching Compose’s behavior. docker compose up, with a docker-compose.yml file committed to the project, means any developer, on any machine, gets the exact same local stack, running, in seconds, git clone and docker compose up away from a fully working environment.
Try It
- Write a Compose file with an
apiservice and adbservice, using a named volume for the database’s data. - Confirm the API can actually reach the database, using the service name (
db) as the hostname, notlocalhost. - Stop the stack with
docker compose down, start it again withdocker compose up, and confirm the database’s data is still there, thanks to the named volume. - Explain, in one or two sentences, why
docker compose down -vis a dangerous command to run against anything other than a local, disposable environment.
Recap
- Docker Compose starts multiple, coordinated containers with one command, each service reachable by the others using its service name as a hostname.
- Named volumes persist data, a database’s contents, across container restarts and rebuilds, without them, that data is lost the moment a container is removed.
- A committed Compose file gives every developer on a project the exact same local stack, in seconds, regardless of what’s installed on their own machine.
Next lesson: exercises, containerizing a complete application and running it with Docker Compose end to end.