Docker Compose Guide: Run Multi-Container Apps with One Command
Learn Docker Compose to define and run multi-container applications. Covers compose.yaml syntax, services, networks, volumes, environment files, and depends_on.

Running a web application means running multiple containers: an API server, a database, maybe a cache layer. Managing all of these with individual docker run commands is tedious and error-prone. Docker Compose solves this by letting you define your entire application stack in a single YAML file, then start everything with one command.
What Is Docker Compose?
Docker Compose is a tool for defining and running multi-container Docker applications. You describe your services, networks, and volumes in a compose.yaml file, then:
docker compose up # start all services
docker compose down # stop and remove containersCompose handles starting containers in the right order, creating a shared network so containers can talk to each other by service name, managing volumes for persistent data, and setting environment variables.
The compose.yaml Structure
services:
service-name:
image: image:tag
# or
build: ./path/to/dockerfile/directory
ports:
- "HOST:CONTAINER"
environment:
- KEY=VALUE
volumes:
- source:target
depends_on:
- other-service
volumes:
volume-name:
networks:
network-name:The top-level keys are services, volumes, and networks. services is the only required section.
A Basic Example: API + Database
Here's a realistic stack: a Node.js API and a PostgreSQL database.
services:
api:
build: .
ports:
- "3000:3000"
environment:
- NODE_ENV=development
- DATABASE_URL=postgresql://myuser:secret@db:5432/myapp
depends_on:
db:
condition: service_healthy
volumes:
- .:/app
- /app/node_modules
db:
image: postgres:16-alpine
environment:
POSTGRES_USER: myuser
POSTGRES_PASSWORD: secret
POSTGRES_DB: myapp
volumes:
- postgres-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U myuser -d myapp"]
interval: 10s
timeout: 5s
retries: 5
volumes:
postgres-data:Start the stack:
docker compose upBoth containers start. The api service waits for db to pass its health check before starting. The API can reach the database at the hostname db — Docker Compose creates a shared network and assigns each service's name as its DNS hostname.
Core compose.yaml Syntax
build vs image
services:
api:
build: . # build from Dockerfile in current directory
redis:
image: redis:7-alpine # use a pre-built image from Docker Hubenvironment and env_file
# Inline environment variables
environment:
NODE_ENV: development
PORT: 3000
# Or from a .env file
env_file:
- .envThe .env file in the same directory as compose.yaml is automatically read by Compose for variable substitution. Never commit .env to source control — add it to .gitignore.
volumes
services:
db:
volumes:
- postgres-data:/var/lib/postgresql/data # named volume (managed by Docker)
- ./init.sql:/docker-entrypoint-initdb.d/init.sql # bind mount
volumes:
postgres-data: # declares the named volumeNamed volumes survive docker compose down by default. To remove them too: docker compose down -v.
depends_on
services:
api:
depends_on:
db:
condition: service_healthy # wait until db health check passes
redis:
condition: service_started # just wait for container to startservice_healthy waits until the container's healthcheck passes — much safer for databases.
healthcheck
healthcheck:
test: ["CMD", "wget", "-qO-", "http://localhost:3000/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 10sEssential Docker Compose Commands
| Command | What It Does |
|---|---|
docker compose up | Start all services |
docker compose up -d | Start in background (detached) |
docker compose up --build | Rebuild images then start |
docker compose down | Stop and remove containers + networks |
docker compose down -v | Also remove volumes |
docker compose ps | List service containers |
docker compose logs | View all service logs |
docker compose logs -f api | Follow logs for the api service |
docker compose exec api sh | Shell into the running api container |
docker compose restart api | Restart a single service |
docker compose pull | Pull latest images |
Development Workflow with Compose
For development, mount your source code as a volume so changes are reflected immediately without rebuilding:
services:
api:
build: .
volumes:
- .:/app # mount source code
- /app/node_modules # anonymous volume prevents host node_modules overwriting
command: npm run dev # overrides Dockerfile CMD to use a watch-mode commandComplete Three-Service Stack
services:
api:
build: .
ports:
- "3000:3000"
env_file: .env
environment:
- NODE_ENV=development
depends_on:
db:
condition: service_healthy
redis:
condition: service_started
volumes:
- .:/app
- /app/node_modules
db:
image: postgres:16-alpine
env_file: .env
volumes:
- postgres-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER}"]
interval: 10s
timeout: 5s
retries: 5
redis:
image: redis:7-alpine
command: redis-server --maxmemory 256mb --maxmemory-policy allkeys-lru
volumes:
postgres-data:Previous: Lesson 4 — Dockerfile Guide | Next: Lesson 6 — What Is Kubernetes?
Part of the Docker & Kubernetes Mastery course.
External references:
