Docker Course
Docker Compose Introduction
You've been running containers one by one — typing docker run five times, each with a dozen flags, then remembering the right order to start them in. There is a better way. Docker Compose lets you define your entire application stack in a single file and start everything with one command.
Every serious Docker project uses Compose. It's not an advanced tool — it's the standard way to run multi-container applications in development and increasingly in production. Once you understand what it does, you'll never want to go back to managing containers manually.
The Problem Compose Solves
Imagine you're starting a typical web application locally — an API, a database, a cache, and a reverse proxy. Without Compose, your startup sequence looks like this:
docker volume create postgres-data
docker run -d --name db --network app-network -v postgres-data:/var/lib/postgresql/data -e POSTGRES_PASSWORD=secret postgres:15-alpine
docker run -d --name redis --network app-network redis:7-alpine
docker run -d --name api --network app-network -p 3000:3000 -e DATABASE_URL=postgresql://postgres:secret@db:5432/app -e REDIS_URL=redis://redis:6379 order-api:v1.0.0
docker run -d --name nginx --network app-network -p 80:80 -v $(pwd)/nginx.conf:/etc/nginx/nginx.conf:ro nginx:alpine
Six commands, dozens of flags, specific startup order, easy to forget something, impossible to share with the team reliably. Now imagine a new developer joining — they have to run all of this correctly just to start the app locally.
With Compose, all of that becomes:
The Orchestra Conductor Analogy
Docker Compose is like a conductor leading an orchestra. Each instrument is a container — the API, the database, the cache, the proxy. Without a conductor, every musician starts whenever they feel ready, plays at their own tempo, and the result is chaos. The conductor — Compose — reads the score (your docker-compose.yml), tells each instrument when to start, what to play, how loudly, and how to coordinate with the others. One baton raise. The whole orchestra plays.
The docker-compose.yml File
Everything Compose knows about your application lives in a file called docker-compose.yml in your project root. It's written in YAML — a human-readable format that uses indentation to define structure. The file describes your services, their images, ports, volumes, networks, and environment variables.
The scenario: You're a full-stack developer at an e-commerce startup. Your application has four components — a Node.js API, a PostgreSQL database, a Redis cache, and an nginx reverse proxy. You need every new team member to be able to start the full stack locally in one command, with the correct networking and volumes already set up.
services: # top-level key — defines all containers in the stack
db: # service name — becomes the DNS hostname on the network
image: postgres:15-alpine # the Docker image to use
environment: # environment variables passed to the container
POSTGRES_PASSWORD: secret123
POSTGRES_DB: orders
volumes:
- postgres-data:/var/lib/postgresql/data # named volume for data persistence
restart: unless-stopped # restart policy — survives crashes and reboots
redis:
image: redis:7-alpine
restart: unless-stopped
api:
build: . # build from the Dockerfile in the current directory
ports:
- "3000:3000" # host:container port mapping
environment:
NODE_ENV: production
DATABASE_URL: postgresql://postgres:secret123@db:5432/orders
REDIS_URL: redis://redis:6379
depends_on: # start db and redis before api
- db
- redis
restart: unless-stopped
nginx:
image: nginx:alpine
ports:
- "80:80"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro # bind mount for config injection
depends_on:
- api
restart: unless-stopped
volumes: # declare named volumes used by services
postgres-data: # Docker creates this volume automatically
[+] Running 5/5 ✔ Network order-api_default Created 0.1s ✔ Volume "order-api_postgres-data" Created 0.0s ✔ Container order-api-db-1 Started 0.8s ✔ Container order-api-redis-1 Started 0.9s ✔ Container order-api-api-1 Started 1.2s ✔ Container order-api-nginx-1 Started 1.4s
What just happened?
One command started your entire application stack. Compose automatically created a custom bridge network (order-api_default) and attached all four containers to it — so they can reach each other by service name immediately. It created the named volume for PostgreSQL. It respected depends_on and started services in the right order — database and Redis first, then API, then nginx. The service names (db, redis, api, nginx) are the DNS hostnames on the network — which is why DATABASE_URL uses @db:5432 instead of an IP address.
The Core Compose Commands
docker compose up # start all services (foreground — logs stream to terminal)
docker compose up -d # start all services detached (background)
docker compose up --build # rebuild images before starting (picks up Dockerfile changes)
docker compose down # stop and remove containers and the network
docker compose down -v # also remove named volumes — WARNING: deletes data
docker compose ps # list running services in the stack
docker compose logs # show logs from all services
docker compose logs -f api # follow logs from a specific service in real time
docker compose restart api # restart a single service without touching the others
docker compose stop # stop all services without removing containers
docker compose start # start stopped services
# docker compose ps output NAME IMAGE STATUS PORTS order-api-db-1 postgres:15-alpine Up 3 minutes order-api-redis-1 redis:7-alpine Up 3 minutes order-api-api-1 order-api Up 3 minutes 0.0.0.0:3000->3000/tcp order-api-nginx-1 nginx:alpine Up 3 minutes 0.0.0.0:80->80/tcp # docker compose logs -f api (last few lines) order-api-api-1 | Payment API v1.0.0 starting... order-api-api-1 | Environment: production order-api-api-1 | Connected to database at db:5432 order-api-api-1 | Connected to Redis at redis:6379 order-api-api-1 | Server listening on port 3000
What just happened?
docker compose ps shows only the services in the current project — not every container on the machine like docker ps does. The container names follow the pattern projectname-servicename-number. The logs confirm everything connected correctly — the API reached both the database at db:5432 and Redis at redis:6379 using the service names as hostnames. This is Docker's built-in DNS at work, automatically configured by Compose when it created the default network.
docker compose down -v Deletes Your Data
docker compose down stops and removes containers and the network — but leaves your named volumes intact. Adding -v also removes the named volumes — permanently deleting all database data, uploaded files, and any other persisted state. Use down freely during development. Use down -v only when you deliberately want to wipe the slate clean.
Teacher's Note
docker compose up --build is the command I run after any Dockerfile change — it rebuilds images before starting, so you're always running the latest code. Without --build, Compose uses whatever image was last built, even if the Dockerfile changed.
Practice Questions
1. The file that defines all services, networks, and volumes in a Docker Compose application is called what?
2. The Compose key that tells Docker to start one service before another is called what?
3. The command that stops all Compose services and removes their containers and network — but leaves named volumes intact — is called what?
Quiz
1. In a docker-compose.yml, the api service uses the hostname db to connect to the database service. No network configuration is written in the file. How does this work?
2. After changing a Dockerfile, which command ensures Compose rebuilds the image before starting the services?
3. A developer runs docker compose down -v at the end of the day thinking it just stops the containers. The next morning the database is empty. Why?
Up Next · Lesson 24
Docker Compose YAML Deep Dive
You've written your first Compose file — now let's go deep on every key the YAML supports, the patterns professionals use, and how to structure a Compose file that scales with your project.