Introduction
Welcome to the sixth article in our Docker series! If you’ve been following along, you should have a good grasp of Docker fundamentals, including images, containers, networking, and volumes. In this part, we’re going to dive into Docker Compose—a powerful tool that takes Docker to the next level by simplifying the orchestration of multiple containers.
This is the sixth article in our Docker series. I hope you’ve been following along! If you missed any of the previous articles, you can catch up by visiting the links below:
- A Beginner’s Guide to Docker: Introduction and Setup for Newbies
- Understanding Docker Networking: Virtual Networks, Ports, and Firewalls Explained
- Creating and Using Docker Containers: A Step-by-Step Guide
- Mastering Dockerfile: Build, Cache, and Optimize Docker Images
- A Guide to Persistent Data in Docker: Data Volumes and Bind Mounts
Be sure to check them out if you haven’t already, and let’s dive into docker compose in this article!
What is Docker Compose?
Docker Compose is a tool that enables you to define and manage multi-container Docker applications. The beauty of Compose lies in its simplicity: it allows you to specify all the services your app needs (such as databases, backend services, and front-end containers) in a single YAML file, and then manage these services with simple commands.
But Docker Compose isn’t just about creating containers. It also helps you connect, network, and manage them with minimal effort. It’s a game-changer when it comes to deploying complex apps that involve multiple interdependent services.
Why Use Docker Compose?
In the previous articles, you learned how to spin up containers using the docker run
command. But as your applications become more complex, managing all these containers individually can quickly become tedious. What if you need to run a web server container, a database container, and perhaps a cache service, all linked together? Typing out each command along with the necessary flags every time can be a hassle.
Docker Compose automates this process, allowing you to:
- Define all your containers in a single file (using YAML).
- Set up networking between containers with minimal configuration.
- Easily manage volumes and environment variables.
- Launch and tear down everything with just a single command (
docker compose up
ordocker compose down
).
Docker Compose Basics: The YAML File
At the heart of Docker Compose is the YAML file. This file is where you define all the services your application needs, along with any volumes, networks, and other configurations. YAML is human-readable and quite intuitive, even for people who haven’t worked with it before.
Here’s the basic structure of a Docker Compose file:
version: "3"
services:
web:
image: nginx
ports:
- "80:80"
database:
image: mysql
environment:
MYSQL_ROOT_PASSWORD: password
Breaking Down the Compose File:
version
: This specifies which version of the Docker Compose format you’re using. As of now, version 3 is commonly used for newer features.services
: This is where you define each container or service your application requires. In the example above, we have two services:web
(runningnginx
) anddatabase
(runningmysql
).image
: Specifies the Docker image to use for the service.ports
: Maps container ports to your machine’s ports. For instance,80:80
exposes port 80 of the container on port 80 of your machine.environment
: Allows you to pass environment variables into the container. Here, we set the root password for the MySQL database.
Compose File Evolution:
In earlier versions, the Docker Compose file was simpler but lacked many of the advanced features we now use, such as networks and volumes. Over time, Docker added new capabilities, leading to more complex and feature-rich configurations. Nowadays, you’ll typically see versions 2 or 3, which support networking, volume management, and more.
Defining Services
At the core of Docker Compose are services. A service is essentially a container, but with more options and flexibility. Let’s explore what a service can contain in a real-world scenario.
Here’s an example for a WordPress setup with a MySQL database:
version: '3'
services:
wordpress:
image: wordpress
ports:
- "8080:80"
environment:
WORDPRESS_DB_HOST: db
WORDPRESS_DB_USER: exampleuser
WORDPRESS_DB_PASSWORD: examplepass
WORDPRESS_DB_NAME: exampledb
db:
image: mysql
environment:
MYSQL_ROOT_PASSWORD: examplepass
Here, you can see:
- Multiple services are defined:
wordpress
anddb
(for the database). - The WordPress service uses environment variables to connect to the
db
service. - Ports: WordPress is accessible on port 8080 of your machine, and the web traffic will be forwarded to port 80 of the container.
Networking in Docker Compose
Another key feature of Docker Compose is how it handles networking. In Docker Compose, each service automatically gets its own network, and services can talk to each other using their service names.
For example, in the WordPress setup above, the wordpress
service can connect to the db
service using the hostname db
—no need to remember IP addresses or configure DNS.
Here’s a simplified view of the networking process:
- Docker creates a virtual network for all your services.
- Each service gets its own IP address and can be accessed by the name you give it in the YAML file.
By default, all services within the same Docker Compose file are connected to each other, making it incredibly easy to orchestrate complex setups without a headache.
What’s Next?
Now that we’ve covered the basics of Docker Compose, we’re ready to dive into the CLI tool, which makes managing containers even more efficient. In the next part of this guide, we’ll explore the actual commands you’ll use to work with Docker Compose. We’ll walk through practical examples that will help you get hands-on experience.
Now, let’s dive into building custom images with Docker Compose, an essential feature that allows you to create Docker images as part of your workflow.
Adding Image Building to Compose Files
One of the most convenient features of Docker Compose is that it can handle the building of Docker images on the fly, which eliminates the need to manually build images before running them. When you run docker compose up
, it will check whether the required images are available locally. If not, it will build them using the specified Dockerfile in your Compose file.
This becomes crucial in scenarios where you want to customize your images — perhaps you want to install additional software or configure your environment in a specific way. Instead of using a pre-built image from Docker Hub, you can specify a Dockerfile that Compose will build automatically.
Let’s break down the process:
Building the Image
By specifying a build
section in your Compose file, Compose knows where to look for the Dockerfile. It will then use that Dockerfile to build the image, ensuring the image is up-to-date and contains all your custom configurations.
For example:
services:
nginx:
build:
context: .
dockerfile: Dockerfile
args:
- NGINX_VERSION=stable
image: nginx-custom
Here, Compose looks for Dockerfile
in the current directory (.
) and builds an image called nginx-custom
. You can also pass build arguments like NGINX_VERSION
for further customization.
Running the Build
When you run docker compose up
, Compose will check if the custom image (nginx-custom
) is already built. If it isn’t, it will build it based on the instructions provided in the Dockerfile.
In cases where you need to force a rebuild, you can use:
docker compose up --build
This command will trigger a rebuild of your custom images, ensuring any changes to your Dockerfile are reflected in the new image.
Optimizing the Build Process
Compose only rebuilds images if necessary — if it doesn’t find an existing image with the specified name or if you explicitly tell it to rebuild. This makes the workflow efficient since you’re not rebuilding the image every time, saving both time and resources.
Advanced Image Builds
While the basics of building custom images with Compose are straightforward, you can enhance your setup by adding custom environment variables or build arguments. These allow for more granular control over the build process and make your images more adaptable to different environments (development, staging, production, etc.).
For instance, you might pass build arguments that affect the image only during the build phase, not during runtime. These are especially useful when different environments need slight variations in their images.
Example Walkthrough
Let’s take a practical example where you’re building a web server stack with an Nginx reverse proxy and an Apache web server. Imagine you want to simulate your production environment as closely as possible in development.
Your docker-compose.yml
file might look something like this:
version: '3'
services:
proxy:
build:
context: .
dockerfile: Dockerfile
image: nginx-custom
ports:
- "80:80"
web:
image: httpd:latest
volumes:
- ./html:/usr/local/apache2/htdocs/
Here, the nginx
service is building a custom Nginx image using the specified Dockerfile (Dockerfile
). The web
service uses the official Apache image and mounts your local HTML files into the container.
The Dockerfile
might look something like this:
FROM nginx:1.26
COPY ./nginx.conf /etc/nginx/conf.d/default.conf
When you run docker compose up
, the Nginx image will be built from the Dockerfile
, using the NGINX version specified in the Compose file. After the image is built, it will be used to spin up a container that reverse proxies to the Apache web server.
This setup allows you to closely replicate the environment your application will run in when deployed to production, which helps to catch potential issues early in the development cycle.
Summary
This article is part of a series on Docker, focusing on Docker Compose, a tool that simplifies managing multi-container applications. Docker Compose allows you to define services, networking, and volumes in a single YAML file, making it easier to run complex apps. The article explains how to create and manage services, like databases and web servers, using Docker Compose, and covers networking between services. It also introduces building custom Docker images with Docker Compose, providing examples and guidance on streamlining the development process.