Skip to main content

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.yml file, 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: web and db.
  • The web service is built from the current directory and maps port 8000.
  • The db service uses the official PostgreSQL image and sets some environment variables.
  • A named volume db_data is used for database persistence.

Common Docker Compose Commands

Here are the most important commands you'll use with Docker Compose:

CommandDescription
docker-compose upStarts all services in the foreground
docker-compose up -dStarts all services in detached mode
docker-compose downStops and removes all services and networks
docker-compose psLists running services
docker-compose logsShows logs from services
docker-compose buildBuilds images for services
docker-compose execRuns 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 app service can refer to redis as 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 resolve db to 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_on only 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 -v to 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

  1. 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

  2. Which keyword in docker-compose.yml is used to specify that one container should start before another?

    • a) link
    • b) start_after
    • c) depends_on
    • d) prerequisite

    Answer: c) depends_on

  3. True or False: By default, all services in a Compose file can communicate with each other using service names as hostnames.

    Answer: True

  4. 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

  5. What is a common mistake when using depends_on in 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