1.07 Docker Compose
Overview
Docker Compose is a tool for defining and running multi-container Docker applications using a single YAML configuration file.
Abstract
Instead of running multiple docker run commands manually and wiring containers together with flags, Docker Compose lets you declare the entire application stack β services, ports, networks, volumes, and dependencies β in one docker-compose.yml file. A single docker-compose up command brings everything up. This is the standard approach for local development and single-host deployments.
Why It Matters in Production
Real applications consist of multiple services: frontends, backends, databases, caches, workers. Managing these with raw docker run commands is error-prone and hard to reproduce. Docker Compose provides:
- Reproducibility β the entire stack is defined as code
- Dependency ordering β control which containers start first
- Isolated networking β services communicate by name without manual linking
- Single command operations β
up,down,logs,pswork across all services
Key Concepts
| Concept | Description |
|---|---|
docker-compose.yml |
YAML file that defines all services, networks, and volumes |
| Service | A container definition β image, ports, environment, networks |
docker-compose up |
Build (if needed) and start all services |
docker-compose down |
Stop and remove containers, networks |
links |
Deprecated way to connect containers; replaced by named networks |
depends_on |
Controls startup order between services (v2+) |
build |
Tells Compose to build the image from a Dockerfile instead of pulling |
| Networks | Isolated communication layers; Compose auto-creates one per project (v2+) |
Sample Application β Voting App
A multi-service stack used throughout this course. It demonstrates how different technologies are wired together:
voting-app (Python) β redis (in-memory DB) β worker (.NET)
β
result-app (Node.js) ββββββββββββββ db (PostgreSQL)
| Service | Technology | Role |
|---|---|---|
voting-app |
Python | Web UI; user casts vote; writes to Redis |
redis |
Redis | In-memory store for incoming votes |
worker |
.NET | Reads from Redis; writes to PostgreSQL |
db |
PostgreSQL | Persistent vote count storage |
result-app |
Node.js | Web UI; reads from PostgreSQL; shows results |
Example Configuration or Commands
Running Manually with docker run
docker run -d --name=redis redis
docker run -d --name=db postgres:9.4
docker run -d --name=vote -p 5000:80 voting-app
docker run -d --name=result -p 5001:80 result-app
docker run -d --name=worker worker
Containers are isolated β they cannot find each other without explicit linking.
Version 1 β Basic Compose with Links
redis:
image: redis
db:
image: postgres:9.4
vote:
image: voting-app
ports:
- 5000:80
links:
- redis
result:
image: result-app
ports:
- 5001:80
links:
- db
worker:
image: worker
links:
- redis
- db
Links are Deprecated
links manually injects /etc/hosts entries into containers. This feature is deprecated. Use named networks (v2+) instead.
Version 2 β Services Section + depends_on
version: "2"
services:
redis:
image: redis
db:
image: postgres:9.4
vote:
image: voting-app
ports:
- 5000:80
depends_on:
- redis
result:
image: result-app
ports:
- 5001:80
worker:
image: worker
- All services join an auto-created bridge network
- Services reference each other by service name (no links needed)
depends_oncontrols startup order
Version 3 β With Environment Variables
version: "3"
services:
redis:
image: redis
db:
image: postgres:9.4
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
vote:
image: voting-app
ports:
- 5000:80
worker:
image: worker-app
result:
image: result-app
ports:
- 5001:80
Version 3 adds Docker Swarm compatibility and removes some v2-only options.
Building Images with Compose
Replace image: with build: to build from a local Dockerfile:
vote:
build: ./vote # path to folder containing Dockerfile
ports:
- 5000:80
links:
- redis
result:
build: ./result
ports:
- 5001:80
worker:
build: ./worker
On docker-compose up, Compose builds the image first, then starts the container.
Networks β Frontend / Backend Separation
version: "2"
services:
redis:
image: redis
networks:
- back-end
db:
image: postgres:9.4
networks:
- back-end
vote:
image: voting-app
networks:
- front-end
- back-end
result:
image: result
networks:
- front-end
- back-end
worker:
image: worker
networks:
- back-end
networks:
front-end:
back-end:
Version Comparison
| Feature | Version 1 | Version 2 | Version 3 |
|---|---|---|---|
version: key required |
No | Yes | Yes |
services: wrapper |
No | Yes | Yes |
| Auto bridge network | No (default) | Yes (dedicated) | Yes (dedicated) |
links needed |
Yes | No | No |
depends_on |
No | Yes | Yes |
| Docker Swarm support | No | No | Yes |
environment variables |
Yes | Yes | Yes |
Best Practices
Best Practices
- Always use version 3 for new projects β it's Swarm-compatible and feature-complete.
- Use named networks to segment traffic (e.g. front-end vs back-end).
- Use
depends_onto express startup order, but note it only waits for the container to start β not for the service inside to be ready. Use health checks for true readiness. - Use
build:during development so Compose manages image builds; switch to pre-built images in CI/CD. - Name containers explicitly for clarity and easy referencing.
- Keep
docker-compose.ymlunder version control β it is infrastructure as code.
Security Best Practices
Security
- Never hardcode passwords in
docker-compose.yml. Use environment variable files (.env) or Docker Secrets (Swarm mode). - Add
.envto.gitignoreto prevent credential leaks. - Avoid exposing database ports (e.g.
5432,6379) to the host unless strictly necessary. - Use internal networks to isolate backend services from direct external access.
- Use read-only mounts where containers do not need write access to volumes.
- Pin image versions (e.g.
postgres:9.4) β avoidlatestin production to prevent unexpected upgrades.
Do and Don't
| β Do | β Don't |
|---|---|
Use version 3 with services: block |
Use version 1 flat format for new projects |
| Use named networks for traffic isolation | Rely on links (deprecated) |
Use depends_on for startup ordering |
Assume depends_on means the service is ready |
Use .env files for secrets |
Hardcode passwords in docker-compose.yml |
Pin image tags (postgres:9.4) |
Use latest in production |
Use build: for local development |
Re-pull pre-built images on every change |
Common Mistakes
Common Mistakes
- Missing links in v1: Forgetting
links:in version 1 causes containers to fail to resolve service hostnames. - Version mismatch: Using v1 syntax (no
services:key) with aversion: "2"header causes parse errors. - Port conflicts: Publishing the same host port twice (e.g. two services on
5000) will fail onup. depends_onis not a health check: The dependent container starts but the service inside (e.g. Postgres) may not yet be accepting connections.- Forgetting the
networks:root block: Defining networks under services without declaring them at root level causes errors.
Troubleshooting
# View running services and their status
docker-compose ps
# Follow logs for all services
docker-compose logs -f
# Follow logs for a single service
docker-compose logs -f vote
# Restart a single service
docker-compose restart worker
# Rebuild images and recreate containers
docker-compose up --build
# Tear down all containers and networks
docker-compose down
# Tear down including volumes
docker-compose down -v
# Validate the compose file syntax
docker-compose config
Quick Recap
- Docker Compose manages multi-container applications via a single YAML file
docker-compose upstarts everything;docker-compose downstops and cleans up- Version 1 uses flat format +
links; Version 2 introducesservices:, auto-networking, anddepends_on; Version 3 adds Swarm support - In v2+, containers discover each other by service name β no
linksrequired - Use
build:to build from local Dockerfiles instead of pulling images - Use separate networks to isolate front-end and back-end traffic
- Secrets belong in
.envfiles or Docker Secrets β never inline in the YAML
Interview / Revision Notes
Revision
Q: What problem does Docker Compose solve?
Managing multi-container apps as a single unit instead of running and wiring individual docker run commands.
Q: How do containers communicate in Compose v2+?
Compose creates a dedicated bridge network. Services resolve each other by service name automatically β no links needed.
Q: What does depends_on do?
It controls start order β the specified service starts before the dependent one. It does not wait for the service to be ready (health checks handle that).
Q: What is the difference between image: and build: in Compose?
image: pulls a pre-built image from a registry. build: builds an image locally from a Dockerfile at the given path.
Q: Why are links deprecated?
Docker's built-in networking (from v2 onwards) handles service discovery by name. Links were a manual workaround that is no longer needed.
Q: How do you separate front-end and back-end traffic in Compose?
Define named networks (front-end, back-end) in a root networks: block and assign each service to the appropriate network(s).