Managing Multi-Container Applications with Docker Compose
Introduction
As you build more complex applications, you'll find that running a single container is rarely enough. Modern software often consists of multiple services—such as web servers, databases, and caches—that need to work together. Managing these interconnected containers manually can quickly become difficult and error-prone.
Docker Compose is a tool designed to help you define and manage multi-container Docker applications. Using a simple YAML file, you can describe your application's services, networks, and volumes, and start everything with a single command.
In this lesson, you'll learn how to use Docker Compose to orchestrate multi-container applications efficiently and reliably.
What is Docker Compose?
Docker Compose is an official Docker tool that allows you to:
- Define multiple services (containers) in a single YAML file (
docker-compose.yml). - Configure networks and volumes for those services.
- Start, stop, and manage the entire application stack with simple commands.
- Simplify development, testing, and deployment processes.
Key Concepts:
- Service: A container configuration (image, environment, volumes, etc.).
- Project: A set of services defined in one
docker-compose.ymlfile, typically representing one application.
Anatomy of a docker-compose.yml File
Here's a simple example for a web application with a separate database:
version: '3.8'
services:
web:
build: .
ports:
- "8000:8000"
volumes:
- .:/code
depends_on:
- db
db:
image: postgres:16
environment:
POSTGRES_USER: example
POSTGRES_PASSWORD: example
POSTGRES_DB: exampledb
volumes:
- db_data:/var/lib/postgresql/data
volumes:
db_data:
Explanation:
- The file defines two services:
webanddb. - The
webservice is built from the current directory and maps port 8000. - The
dbservice uses the official PostgreSQL image and sets some environment variables. - A named volume
db_datais used for database persistence.
Common Docker Compose Commands
Here are the most important commands you'll use with Docker Compose:
| Command | Description |
|---|---|
docker-compose up | Starts all services in the foreground |
docker-compose up -d | Starts all services in detached mode |
docker-compose down | Stops and removes all services and networks |
docker-compose ps | Lists running services |
docker-compose logs | Shows logs from services |
docker-compose build | Builds images for services |
docker-compose exec | Runs a command in a running service |
Example Usage:
docker-compose up -d
docker-compose logs web
docker-compose exec db psql -U example -d exampledb
docker-compose down
Defining and Linking Multiple Services
When your application requires multiple containers (e.g., a web server and a database), Compose provides a simple way to define how they interact.
Example: Web App with Redis Cache
version: '3.8'
services:
app:
build: .
ports:
- "5000:5000"
environment:
REDIS_HOST: redis
redis:
image: "redis:alpine"
- The
appservice can refer toredisas the hostname (Compose automatically creates a network). - Environment variables are used for configuration.
Networking in Docker Compose
By default, all services in a docker-compose.yml file are connected to the same network and can communicate using their service names as hostnames.
Example:
- If your web service connects to a database at
db:5432, Compose will resolvedbto the correct IP.
Volumes in Docker Compose
You can define and mount volumes for persistent or shared data.
Example:
services:
db:
image: postgres:16
volumes:
- db_data:/var/lib/postgresql/data
volumes:
db_data:
This ensures your database data persists even if the container is removed.
Service Dependencies
The depends_on keyword specifies dependencies between services. It ensures that a service starts only after its dependencies have started (but does not guarantee that the dependency is ready).
Example:
services:
web:
build: .
depends_on:
- db
db:
image: postgres
Pitfall:
depends_ononly controls startup order. Use health checks or wait-for scripts if you need a service to be ready before another starts.
Environment Variables and Configuration
You can pass environment variables directly or from an external file.
Direct:
environment:
- DEBUG=true
- SECRET_KEY=mysecret
From a file (.env):
env_file:
- .env
Scaling Services
You can scale stateless services (like web servers) easily:
docker-compose up --scale web=3
This will start 3 instances of the web service.
Common Mistakes and Pitfalls
- Forgetting to use named volumes: Data can be lost if you use only anonymous volumes.
- Relying solely on
depends_on: This does not wait for a service to be ready. - Hardcoding ports: Avoid port conflicts by specifying only the ports you need to expose.
- Not cleaning up resources: Use
docker-compose down -vto remove named volumes if you want a clean state.
Real-World Use Case: Local Development with Compose
Scenario: You’re building a Django application with a PostgreSQL backend and Redis cache.
Compose file:
version: '3.8'
services:
web:
build: .
ports:
- "8000:8000"
volumes:
- .:/code
environment:
- DEBUG=1
- DATABASE_URL=postgres://user:password@db:5432/appdb
- REDIS_URL=redis://redis:6379/0
depends_on:
- db
- redis
db:
image: postgres:16
environment:
- POSTGRES_DB=appdb
- POSTGRES_USER=user
- POSTGRES_PASSWORD=password
volumes:
- db_data:/var/lib/postgresql/data
redis:
image: redis:alpine
volumes:
db_data:
Benefits:
- All services can be started with a single command.
- Easy to reset environment or add new services later.
- Development/production parity.
Summary
Docker Compose is an essential tool for developing, testing, and deploying multi-container applications. It simplifies configuration, orchestration, and management of complex application stacks. By defining services, networks, and volumes in a single YAML file, you can efficiently manage your application's lifecycle.
Key Takeaways:
- Use Compose to manage multi-service Docker applications.
- Define services, networks, and persistent volumes in
docker-compose.yml. - Use
depends_on, but be aware of its limitations. - Scale stateless services easily.
- Clean up resources properly.
Quiz
-
What is the primary purpose of Docker Compose?
- a) To build Docker images faster
- b) To define and manage multi-container Docker applications
- c) To monitor container performance
- d) To secure Docker containers
Answer: b) To define and manage multi-container Docker applications
-
Which keyword in
docker-compose.ymlis used to specify that one container should start before another?- a) link
- b) start_after
- c) depends_on
- d) prerequisite
Answer: c) depends_on
-
True or False: By default, all services in a Compose file can communicate with each other using service names as hostnames.
Answer: True
-
How do you remove all containers, networks, and named volumes defined in a Compose file?
- a)
docker-compose down - b)
docker-compose stop - c)
docker-compose rm - d)
docker-compose down -v
Answer: d)
docker-compose down -v - a)
-
What is a common mistake when using
depends_onin Compose?- a) Assuming it waits for the service to be fully ready before starting the dependent service
- b) Using it with volumes
- c) Forgetting to specify ports
- d) Not including environment variables
Answer: a) Assuming it waits for the service to be fully ready before starting the dependent service