A Beginner's Guide to Docker Compose
Learn how to use Docker Compose to manage multi-container setups. Help overcome the need of running multiple docker-run statements to manage applications.
Join the DZone community and get the full member experience.
Join For FreeThe constant need to run docker run in multiple terminals for managing multi-container setups.
If you’re new to Docker, you’ve likely already discovered the docker run command that makes it easy to run a single container. However, what happens if your app requires a database? A cache? Before you know it, you’re dealing with a terminal tab nightmare:
- Tab 1 has a lengthy
docker runcommand to spin up your database. - Tab 2 also has a
docker runcommand that launches your cache. - In the third tab, there’s a giant
docker runcommand for your web app that you’re attempting to link to the other two.
This is only going to get more out of control. It’s a setup that is simple to forget, prone to miss, and difficult to convey to new teammates. This is where Docker Compose comes into play. With only one command, it allows you to define and execute your whole multi-container app.
What Exactly Is Docker Compose?
The way to visualize Docker Compose is similar to a cookbook recipe for a dish.
This configuration/recipe is written in a single file called the docker-compose.yml. (YAML is a form of configuration file.)
The recipe mentions the list of ingredients required for the application to run. In Docker nomenclature, these are called the "services." A service is just a running container. For example:
mywebapp: Your Python, Node.js, or Java application.db: A Postgres or MySQL database.cache: A Redis cache.
By defining all the services/configurations in one key file, you have the following advantages:
- Single command: You can type just
docker-compose upto create and start every application in the right order, along with their dependencies. Stopping it is also easy, just typedocker-compose down. - Automatic management of networks: Docker Compose puts all the necessary services in a private virtual network. This means that your
mywebappservice can easily talk with yourdbservice just by using the name "db." It makes inter-process communication very easy.
Here is a simple demonstration of how effective this is:
Let's start with the classic "Hello World" example. Every time you refresh the webpage, its counter should increase. This application has two simple services:
mywebapp: A simple flask-based application that displays the view count.redis: A Redis database that actually stores the counter.
Here's the project structure:
/hello-docker
|-- app.py
|-- requirements.txt
|-- Dockerfile
|-- docker-compose.yml
Step 1: The Python App (app.py)
This is a tiny web app. Notice the host='redis' is the way we are talking with Redis. This is the service name that we have defined in the Docker Compose file.
# app.py
from flask import Flask
from redis import Redis
import os
# A vanilla Flask app
app = Flask(__name__)
# We connect to the service named 'redis'
redis = Redis(host='redis', port=6379)
@app.route('/')
def hello():
# 'redis.incr' increases the number by 1
count = redis.incr('hits')
return f'Hello from Docker-Compose! The total count is {count}.'
if __name__ == "__main__":
# Run the app on port 5000. This can be any port open in your system.
app.run(host="0.0.0.0", port=5000)
requirements.txt
flask
redis
Step 2: The Dockerfile
This is the "instruction manual" for building our own web service. It's just a standard Python Dockerfile.
# Dockerfile
FROM python:3.10-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
# Run the app when the container starts
CMD ["python", "app.py"]
Step 3: Docker Doing All the Magic With docker-compose.yml
This is the classic recipe file. This is where all the magic actually happens.
# docker-compose.yml
version: '3.9' # Specifies the docker-compose version
services:
# Our first service: the web app
mywebapp:
build: .
ports:
- "5000:5000"
depends_on:
- redis
# The redis database
redis:
image: "redis:alpine"
Let's break this down line by line:
services:: These define the top-level services that we are going to interact with in our app.mywebapp:: The name we give to our first service.build: .: "This builds this service from the local current directory."ports: ["5000:5000"]: "This line tells to connect the local computer's port 5000 to the container's port 5000 (where the Flask application is running)."depends_on: [redis]: "This tells not to start the application until the Redis application is up and running."
redis:: The name given to our Redis database service.image: "redis:alpine": "Use the latestredis:alpineimage fromdocker-hub. We are not rebuilding the wheel again. Docker Hub is the place where all the official images are stored."
Step 4: Run It!
Now for the best part. Make sure you are in your project directory (/hello-docker) in your terminal.
Start the application:
docker-compose up
Add -d to run it in the background: docker-compose up -d.
Docker will build your web image, pull the Redis image, and start both containers. Open your browser. Go to http://localhost:5000 — you should see “Hello from Docker-Compose! The total count is {}”. Refresh the page, and the count will go up! Your web container is successfully talking to your Redis container. Stop the application. When you’re finished, press Ctrl+C in your terminal. If you ran it in the background, type the following:
docker-compose down
This command stops and removes all the containers and the network it created.
Conclusion
These are the basics of Docker Compose. With just a single file and one command, we can easily maintain and manage multi-container setups. With just a single command, we have effectively eliminated the need to run multiple docker-run, helping drive productivity in our local dev setups.
Any complex software can be written in terms of containerizing it with the help of Docker Compose. Things like adding caches, databases, or any other service can be done by adding just a few lines of YAML code.
Happy Docker Compose-ing on your next coding journeys.
Opinions expressed by DZone contributors are their own.
Comments