Set up a Kubernetes cluster on AWS EKS with eksctl: prerequisites, one-command cluster creation, …
Docker Compose: Multi-Container Apps Made Simple Docker Compose: Multi-Container Apps Made Simple

Summary
Running one container with docker run is easy. Running a web app plus its database plus a cache — each needing the right flags, network, and startup order — turns into a wall of fragile shell commands. Docker Compose fixes that by describing your whole stack in a single YAML file you can start with one command.
This Docker Compose guide is hands-on. You will feel the pain of wiring containers together by hand, then replace it with a compose.yaml file that defines a web service, Postgres, and Redis — with networking, persistent storage, and environment variables handled for you.

The Problem Compose Solves
Say your app needs three containers. By hand, that looks like this:
docker network create appnet
docker run -d --name db --network appnet -e POSTGRES_PASSWORD=secret postgres:16
docker run -d --name cache --network appnet redis:7
docker run -d --name web --network appnet -p 8080:8080 -e DATABASE_URL=... myapp
Every restart means retyping (or scripting) all of it. There’s no single source of truth, teammates set it up differently, and tearing it down cleanly is its own chore. Compose replaces all of that with one declarative file.
Expand your knowledge with Go + Nginx: Deploy a Go API Behind a Reverse Proxy
docker compose (with a space). The old standalone docker-compose (with a hyphen) is v1 and is deprecated — the file format is the same.Your First compose.yaml
Create a file named compose.yaml in your project root. This defines the same three-container stack declaratively:
services:
web:
build: . # build from the local Dockerfile
ports:
- "8080:8080"
environment:
- DATABASE_URL=postgres://app:secret@db:5432/app
- REDIS_URL=redis://cache:6379
depends_on:
- db
- cache
db:
image: postgres:16
environment:
- POSTGRES_USER=app
- POSTGRES_PASSWORD=secret
- POSTGRES_DB=app
volumes:
- pgdata:/var/lib/postgresql/data
cache:
image: redis:7
volumes:
pgdata:
Start the entire stack with one command:
docker compose up -d # -d runs in the background (detached)
docker compose ps # see what's running
docker compose logs -f web # follow one service's logs
That’s the whole value proposition: three coordinated containers, one file, one command.
Deepen your understanding in Build and Deploy a Go Lambda Function
How Services Talk to Each Other
Notice the DATABASE_URL points at db:5432, not an IP. Compose creates a shared network for the project and registers each service name as a DNS hostname. So web reaches Postgres at db and Redis at cache — automatically.
graph LR
subgraph Compose network
W[web :8080] --> D[(db - postgres :5432)]
W --> C[(cache - redis :6379)]
end
U[Your browser] -->|localhost:8080| W
This is why you should never hard-code container IPs. Service names are stable; IPs are not.
Explore this further in Kubernetes Fundamentals: Pods, Deployments, Services
Persisting Data with Volumes
Containers are ephemeral — delete one and its filesystem is gone. That’s fine for the web service but catastrophic for the database. The pgdata named volume mounted at Postgres’s data directory keeps your data across restarts and rebuilds.
Discover related concepts in Database Scaling: From 100K to 5M Users in 18 Months
docker compose down # stops containers, KEEPS the volume (data safe)
docker compose up -d # data is still there
docker compose down -v adds -v, which deletes named volumes — wiping your database. It’s great for a clean reset, but run it on purpose, never on autopilot.Environment Variables and the .env File
Hard-coding secret in the file is fine for a demo, but real projects pull config from a .env file that Compose reads automatically:
# .env (do NOT commit this)
POSTGRES_PASSWORD=supersecret
db:
image: postgres:16
environment:
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
Add .env to your .gitignore. Commit a .env.example with blank values so teammates know what to set.
Uncover more details in Jenkinsfile with envsubst: Simpler CI/CD Config
Startup Order Is Not Readiness
depends_on controls start order, but here’s the trap that bites everyone: it does not wait for a dependency to be ready. Postgres can be “started” yet still be initializing and refusing connections, so your web app crashes on boot. Fix it with a healthcheck:
Journey deeper into this topic with Fix Kubernetes CrashLoopBackOff: Causes and Solutions
db:
image: postgres:16
healthcheck:
test: ["CMD-SHELL", "pg_isready -U app"]
interval: 5s
retries: 5
web:
build: .
depends_on:
db:
condition: service_healthy # now web waits until db actually accepts connections
The Commands You’ll Use Daily
docker compose up -d --build # rebuild images and (re)start everything
docker compose ps # list services + status + ports
docker compose logs -f # tail logs from all services
docker compose exec db psql -U app # open a shell/CLI inside a running service
docker compose restart web # restart a single service
docker compose stop # stop without removing
docker compose down # stop and remove containers + network
docker compose down -v # ...and delete volumes (data)
Common Docker Compose Pitfalls
- Scaling a service with a fixed host port.
docker compose up --scale web=3fails ifwebmaps"8080:8080", because three containers can’t share one host port. Use a range or put a load balancer in front. - Committing secrets. The
.envfile and inline passwords end up in Git history. Use.env(ignored) and.env.example(committed). - Assuming
depends_onwaits for readiness. It doesn’t — add healthchecks (see above). - Losing data to
down -v. Only use-vwhen you intend to wipe volumes. - Editing the wrong file. With override files, remember Compose merges them; check
docker compose configto see the final, resolved configuration.
When to Graduate Beyond Compose
Compose is ideal for local development and small single-host deployments. Once you need self-healing across multiple machines, rolling updates, and autoscaling, that’s the job of an orchestrator — see Kubernetes fundamentals: pods, deployments, and services. If you want to understand what a container actually is under the hood, containers from scratch in Go builds the primitives by hand, and for log handling in containerized apps, NGINX logs and Docker is a practical companion.
What's the most complex multi-container stack you've defined in a single Docker Compose file?
Master this concept through Sed for JSON: Emergency Patterns When jq Is Unavailable
References and Further Reading
- Docker Inc. Docker Compose overview. Docker Documentation.
- Docker Inc. Compose file reference. Docker Documentation.
- Docker Inc. Control startup order with healthchecks. Docker Documentation.
- Docker Inc. Manage data in Docker (volumes). Docker Documentation.
Similar Articles
Related Content
More from devops
Kubernetes CrashLoopBackOff explained: a step-by-step workflow to diagnose it and fix the six most …
Learn Kubernetes fundamentals hands-on: deploy your first pod, understand Deployments and …
You Might Also Like
A practical kubectl cheat sheet: 30+ essential commands for pods, deployments, services, logs, and …
Knowledge Quiz
Test your general knowledge with this quick quiz!
A set of multiple-choice questions to test your knowledge.
Take as much time as you need.
Your score will be shown at the end.
Question 1 of 5
Quiz Complete!
Your score: 0 out of 5
Loading next question...
Contents

