How to use Docker Compose for multi-container applications

Compose simplifies scaling and deployment of applications in Docker by automating container management. Our tutorial takes an in-depth look at setting up and using Docker Compose to streamline your application deployment process.

What is Docker Compose?

Docker Compose is used to manage applications and increase efficiency in container development. Configurations are defined in a single YAML file, making applications easy to build and scale. Docker Compose is often used to set up a local environment. However, it can also be part of a Continuous Integration / Continuous Delivery (CI/CD) workflow. Developers can define a specific container version for testing or specific pipeline phases. This makes it easier to identify issues and fix bugs before the application moves into production.

Docker Compose requirements

For container orchestration, you need both Docker Engine and Docker Compose. Ensure you’ve got one of the following installed on your system:

  • Docker Engine and Docker Compose: Can be installed as standalone binaries.
  • Docker Desktop: Development environment with graphical user interface including Docker Engine and Docker Compose.
Tip

Find out how to install Docker Compose on different operating systems in our tutorials:

Step-by-step guide of how to use Docker Compose

In the following, we demonstrate how to use Docker Compose with a simple Python web application that utilises a hit counter. To do this, we use the Python Flask framework and the Redis in-memory database. You don’t need to install Python or Redis, as they are provided as Docker images.

Step 1: Create project files

Launch the terminal and create a new folder for the project.

$ mkdir composedemo
shell

Change to the directory.

$ cd composedemo
shell

Create the file app.py in this folder and add the following code to it:

import time
import redis
from flask import Flask
app = Flask(__name__)
cache = redis.Redis(host='redis', port=6379)
def get_hit_count():
    retries = 5
    while True:
        try:
            return cache.incr('hits')
        except redis.exceptions.ConnectionError as exc:
            if retries == 0:
                raise exc
            retries -= 1
            time.sleep(0.5)
@app.route('/')
def hello():
    count = get_hit_count()
    return 'Hello World! I was here {} times.\n'.format(count)
python

In our setup, we utilise redis as the hostname and the default port 6379 for connecting to the Redis service. Additionally, we specify that the get_hit_count() function should make multiple connection attempts to the service. This is recommended where Redis may not be immediately available when the application starts or there may be intermittent connection issues during runtime.

Create the file requirements.txt with the dependencies:

flask
redis
plaintext

Step 2: Set up Dockerfile

The Dockerfile is used for the Docker image. This specifies all the dependencies that the Python application requires.

FROM python:3.7-alpine
WORKDIR /code
ENV FLASK_APP=app.py
ENV FLASK_RUN_HOST=0.0.0.0
RUN apk add --no-cache gcc musl-dev linux-headers
COPY requirements.txt requirements.txt
RUN pip install -r requirements.txt
EXPOSE 5000
COPY . .
CMD ["flask", "run"]
shell

We instruct Docker to utilise the Python 3.7 image. Furthermore, we set the necessary environment variables for the flask command. By using apk add, we install essential dependencies, including gcc. To allow the container to monitor port 5000, we specify EXPOSE. Using COPY, we transfer the contents of the current folder to the working directory /code within the container. Finally, as default command for the container we choose flask run.

Check that the Dockerfile was saved without a file extension, as some editors automatically append the .txt suffix.

Step 3: Create YAML file

In docker-compose.yml we configure the services ‘redis’ and ‘web’.

version: "3.9"
services:
    web:
        build: .
        ports:
            - "8000:5000"
    redis:
        image: "redis:alpine"
yaml

The web service is built using the Docker image created by the Dockerfile. It associates the container and the host computer with port 8000, while the Flask web server runs on port 5000. The Redis image, on the other hand, is obtained directly from the official Docker Hub.

Step 4: Run the application with Compose

Launch the application from your project folder.

docker compose up
shell

Call up http://localhost:8000 in your browser. You can also enter http://127.0.0.1:8000.

You should see the following message:

Docker Compose Application: Output the number of visits in the browser
You’ll see the number of times you have visited the browser.

Refresh the page. The number of views should now have increased by 1.

Calling the Docker Compose application again
The number of visits increased by 1.

Stop the application using:

$ docker compose down
shell

To stop running the application, you can simply press Ctrl + C in the terminal.

Step 5: Add a bind mount

If you want to add a bind mount for the web service, you can do this in docker-compose.yml.

version: "3.9"
services:
    web:
        build: .
        ports:
            - "8000:5000"
        volumes:
            - .:/code
        environment:
            FLASK_DEBUG: "true"
    redis:
        image: "redis:alpine"
yaml

Under the Volumes section, we specify the attachment of the current project folder to the /code directory inside the container. This allows for seamless code changes without the need to recreate the image. The variable FLASK_DEBUG tells flask run to run in development mode.

Step 6: Rebuild and run application

Enter the following command in the terminal to rebuild the Compose file:

docker compose up
shell

Step 7: Update the application

Now that you’re using a bind mount for your application, you can modify your code and automatically see changes without rebuilding the image.

Write a new welcome test in app.py.

return 'Hello from Docker! I was here {} times.\n'.format(count)
python

Refresh the browser to test whether the changes have been applied.

Docker Compose application: modified welcome text
The welcome text in the Python application has been modified

Step 8: other commands

The --help option lists available Docker Compose commands:

docker compose --help
shell

To run Docker Compose in the background, you can add the -d argument:

docker compose up -d
shell

Use down to remove all containers. The --volumes option deletes the volumes used by the Redis container.

docker compose down --volumes
shell
Tip

To get started with Docker, check out our Docker Tutorial and our overview of Docker Commands.

Was this article helpful?
Page top