Docker Compose is a powerful tool for defining and running multi-container Docker applications. With Compose, you use a YAML file to configure your application's services, networks, and volumes. Then, with a single command, you create and start all the services from your configuration.
Note: Docker Compose is now integrated into Docker CLI. The new command is
docker compose
instead ofdocker-compose
. We'll use the new command throughout this chapter.
- Simplicity: Define your entire application stack in a single file.
- Reproducibility: Easily share and version control your application configuration.
- Scalability: Simple commands to scale services up or down.
- Environment Consistency: Ensure development, staging, and production environments are identical.
- Workflow Improvement: Compose can be used throughout the development cycle for testing, staging, and production.
The docker-compose.yml
file is the core of Docker Compose. It defines all the components and configurations of your application. Here's a basic example:
version: '3.8'
services:
web:
build: .
ports:
- "5000:5000"
volumes:
- .:/code
environment:
FLASK_ENV: development
redis:
image: "redis:alpine"
Let's break down this example:
version
: Specifies the Compose file format version.services
: Defines the containers that make up your app.web
: A service based on an image built from the Dockerfile in the current directory.redis
: A service using the public Redis image.
- Services: Containers that make up your application.
- Networks: How your services communicate with each other.
- Volumes: Where your services store and access data.
-
docker compose up
: Create and start containersdocker compose up -d # Run in detached mode
-
docker compose down
: Stop and remove containers, networks, images, and volumesdocker compose down --volumes # Also remove volumes
-
docker compose ps
: List containers -
docker compose logs
: View output from containersdocker compose logs -f web # Follow logs for the web service
You can use .env files or set them directly in the compose file:
version: '3.8'
services:
web:
image: "webapp:${TAG}"
environment:
- DEBUG=1
Use extends
to share common configurations:
version: '3.8'
services:
web:
extends:
file: common-services.yml
service: webapp
Ensure services are ready before starting dependent services:
version: '3.8'
services:
web:
image: "webapp:latest"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost"]
interval: 1m30s
timeout: 10s
retries: 3
start_period: 40s
version: '3.8'
services:
db:
image: mysql:5.7
volumes:
- db_data:/var/lib/mysql
restart: always
environment:
MYSQL_ROOT_PASSWORD: somewordpress
MYSQL_DATABASE: wordpress
MYSQL_USER: wordpress
MYSQL_PASSWORD: wordpress
wordpress:
depends_on:
- db
image: wordpress:latest
ports:
- "8000:80"
restart: always
environment:
WORDPRESS_DB_HOST: db:3306
WORDPRESS_DB_USER: wordpress
WORDPRESS_DB_PASSWORD: wordpress
WORDPRESS_DB_NAME: wordpress
volumes:
db_data: {}
Let's break this down in detail:
-
Version:
version: '3.8'
specifies the version of the Compose file format. Version 3.8 is compatible with Docker Engine 19.03.0+. -
Services: We define two services:
db
andwordpress
.a. db service:
image: mysql:5.7
: Uses the official MySQL 5.7 image.volumes
: Creates a named volumedb_data
and mounts it to/var/lib/mysql
in the container. This ensures that the database data persists even if the container is removed.restart: always
: Ensures that the container always restarts if it stops.environment
: Sets up the MySQL environment variables:MYSQL_ROOT_PASSWORD
: Sets the root password for MySQL.MYSQL_DATABASE
: Creates a database named "wordpress".MYSQL_USER
andMYSQL_PASSWORD
: Creates a new user with the specified password.
b. wordpress service:
depends_on
: Ensures that thedb
service is started before thewordpress
service.image: wordpress:latest
: Uses the latest official WordPress image.ports
: Maps port 8000 on the host to port 80 in the container, where WordPress runs.restart: always
: Ensures the container always restarts if it stops.environment
: Sets up WordPress environment variables:WORDPRESS_DB_HOST
: Specifies the database host. Note the use ofdb:3306
, wheredb
is the service name of our MySQL container.WORDPRESS_DB_USER
,WORDPRESS_DB_PASSWORD
,WORDPRESS_DB_NAME
: These match the MySQL settings we defined in thedb
service.
-
Volumes:
db_data: {}
: This creates a named volume that Docker manages. It's used to persist the MySQL data.
To run this setup:
- Save the above YAML in a file named
docker-compose.yml
. - In the same directory, run
docker compose up -d
. - Once the containers are running, you can access WordPress by navigating to
http://localhost:8000
in your web browser.
This setup provides a complete WordPress environment with a MySQL database, all configured and ready to use. The use of environment variables and volumes ensures that the setup is both flexible and persistent.
version: '3.8'
services:
flask:
build: ./flask
environment:
- FLASK_ENV=development
volumes:
- ./flask:/code
redis:
image: "redis:alpine"
nginx:
image: "nginx:alpine"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
ports:
- "80:80"
depends_on:
- flask
networks:
frontend:
backend:
volumes:
db-data:
Let's break this down:
-
Version: As before, we're using version 3.8 of the Compose file format.
-
Services: We define three services:
flask
,redis
, andnginx
.a. flask service:
build: ./flask
: This tells Docker to build an image using the Dockerfile in the./flask
directory.environment
: SetsFLASK_ENV=development
, which enables debug mode in Flask.volumes
: Mounts the local./flask
directory to/code
in the container. This is useful for development as it allows you to make changes to your code without rebuilding the container.
b. redis service:
image: "redis:alpine"
: Uses the official Redis image based on Alpine Linux, which is lightweight.
c. nginx service:
image: "nginx:alpine"
: Uses the official Nginx image based on Alpine Linux.volumes
: Mounts a localnginx.conf
file to/etc/nginx/nginx.conf
in the container. The:ro
flag makes it read-only.ports
: Maps port 80 on the host to port 80 in the container.depends_on
: Ensures that theflask
service is started before Nginx.
-
Networks: We define two networks:
frontend
andbackend
. This allows us to isolate our services. For example, we could put Nginx and Flask on the frontend network, and Flask and Redis on the backend network. -
Volumes:
db-data
: This creates a named volume. Although it's not used in this configuration, it's available if we need persistent storage, perhaps for a database service we might add later.
To use this setup:
- You need a Flask application in a directory named
flask
, with a Dockerfile to build it. - You need an
nginx.conf
file in the same directory as yourdocker-compose.yml
. - Run
docker compose up -d
to start the services.
This configuration sets up a Flask application server, with Redis available for caching or as a message broker, and Nginx as a reverse proxy. The Flask code is mounted as a volume, allowing for easy development. Nginx handles incoming requests and forwards them to the Flask application.
The use of Alpine-based images for Redis and Nginx helps to keep the overall image size small, which is beneficial for deployment and scaling.
This setup is particularly useful for developing and testing a Flask application in an environment that closely mimics production, with a proper web server (Nginx) in front of the application server (Flask) and a caching/messaging system (Redis) available.
- Use version control for your docker-compose.yml file.
- Keep development, staging, and production environments as similar as possible.
- Use build arguments and environment variables for flexibility.
- Leverage healthchecks to ensure service dependencies are met.
- Use
.env
files for environment-specific variables. - Optimize your images to keep them small and efficient.
- Use docker-compose.override.yml for local development settings.
Docker Compose can scale services with a single command:
docker compose up -d --scale web=3
This command would start 3 instances of the web
service.
By default, Compose sets up a single network for your app. Each container for a service joins the default network and is both reachable by other containers on that network, and discoverable by them at a hostname identical to the container name.
You can also specify custom networks:
version: '3.8'
services:
web:
networks:
- frontend
- backend
db:
networks:
- backend
networks:
frontend:
backend:
Compose also lets you create named volumes that can be reused across multiple services:
version: '3.8'
services:
db:
image: postgres
volumes:
- data:/var/lib/postgresql/data
volumes:
data:
Docker Compose simplifies the process of managing multi-container applications, making it an essential tool for developers working with Docker. By mastering Docker Compose, you can streamline your development workflow, ensure consistency across different environments, and easily manage complex applications with multiple interconnected services.
Remember to always use the latest docker compose
command instead of the older docker-compose
, as it's now integrated directly into Docker CLI and offers improved functionality and performance.