Docker compose is a 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.
In this tutorial, you'll learn how to use Docker Compose to manage a multi-service Python web application.
- The application uses the Flask framework and Redis for a hit counter.
- Redis is an open-source, in-memory no-sql databases that can be used as a database, cache, and message broker.
- You'll create a basic Python web application and use Docker Compose to build and run the application.
-
Create a Project Directory:
mkdir composetest cd composetest -
Create
app.py:In your project directory, create a file named
app.pyand paste the following code: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(): # print the hostname and IP address of the container import socket hostname = socket.gethostname() ip_address = socket.gethostbyname(hostname) count = get_hit_count() return_msg = ( f"Hostname: {hostname}<br/>" f"IP Address: {ip_address}<br/>" f"Hello World! I have been seen {count} times.<br/>" ) return return_msg if __name__ == "__main__": app.run(host="0.0.0.0", port=5000)
Explanation:
redisis the hostname of the Redis container on the application's network.- The default port
6379is used for Redis. - Redis provides various data structures, including strings, hashes, lists, sets, and sorted sets for storing data. In the above example, 'hits' is the key Redis string used to store the hit count in Redis.
Note:
Theget_hit_countfunction includes a retry mechanism. This basic loop attempts the request multiple times if the Redis service is unavailable, enhancing the application's resilience during startup and unexpected Redis restarts. -
Create
requirements.txt:In your project directory, create a file named
requirements.txtand add:flask redis -
Create a
Dockerfile:Create a file named
Dockerfile(ensure it has no file extension like.txt) in your project directory and paste the following:FROM python:3.10-slim WORKDIR /code COPY requirements.txt requirements.txt RUN pip install -r requirements.txt COPY . . CMD ["python", "app.py"]
Here's a breakdown of the Dockerfile:
- FROM: Specifies the base image to use for the container.
- WORKDIR: Sets the working directory inside the container.
- COPY: Copies the
requirements.txtfile to the container. - RUN: Installs the Python dependencies listed in
requirements.txt. - EXPOSE: Exposes port 80 to allow external connections.
- COPY: Copies the project files into the container. The first
.refers to the current directory on the host machine, and the second.refers to the working directory inside the container. - CMD: Specifies the command to run when the container starts.
For more details on writing Dockerfiles, refer to the Dockerfile Reference.
Docker Compose simplifies the management of your entire application stack by allowing you to define services, networks, and volumes in a single YAML configuration file.
-
Create
docker-compose.yaml:In your project directory, create a file named
docker-compose.yamland add the following:services: web: build: . ports: - "8000:5000" redis: image: "redis:alpine" container_name: redis_container volumes: - ./redis_data:/data
Explanation:
-
web Service:
- Build: Uses the Dockerfile in the current directory to build the image.
- Ports: Binds port
8000on the host to port5000in the container.
-
redis Service:
- Image: Uses the official Redis image (
redis:alpine) from Docker Hub.
- Image: Uses the official Redis image (
-
With Docker Compose, you can create and start all the services defined in your configuration file with a single command.
-
Start the Application:
From your project directory, run:
docker compose up
-
Access the Application: In GitHub codespaces, navigate to the URL mapped to localhost port 5000. In your local machine, navigate to http://localhost:8000/.
Sample output:
Hostname: 8d3c4f32f076 IP Address: 172.21.0.3 Hello World! I have been seen 1 times. -
Interact with the Application:
-
Refresh the Page: The hit counter should increment with each refresh.
Hello World! I have been seen 2 times.
-
-
List Docker Images:
Open another terminal window and run:
docker image ls
-
Show Running Containers:
In a new terminal window, run:
docker ps
-
Examining the redis container:
- Access the Redis CLI: Run
docker exec -it redis_container redis-clito access the Redis CLI.- The
execcommand allows you to run commands inside a container - The
-itflag allocates an interactive terminal to the container. - The "redis-container" is the name of the Redis container.
- The
redis-clicommand opens the Redis command-line interface.
- The
- Check the Hit Count: Run
get hitsto view the current hit count. - Exit the CLI: Type
exitto leave the Redis CLI.
- Access the Redis CLI: Run
-
Stop the Application:
- Option 1: Run
docker compose downin your project directory. - Option 2: Press
CTRL+Cin the terminal where you started the app.
- Option 1: Run
Docker containers are ephemeral by design, meaning that any data stored within a container is lost when the container is removed. To ensure the data is retained after the container is stopped or removed, you can use bind mounts or volumes to persist data outside the container.
Bind mounts map a directory on the host machine to a directory in the container. Changes to the files in the host directory are immediately reflected in the container and vice versa.
The docker-compose.yaml we have created is using a bind Mount:
services:
redis:
image: "redis:alpine"
volumes:
- ./redis_data:/dataExplanation:
- Bind Mount: Maps the
./redis_datadirectory on the host to/datain the Redis container. - Advantages: Facilitates direct access to data files from the host, useful for debugging or backup purposes.
Volumes are managed by Docker and provide a more robust solution for data persistence. They are stored in a part of the host filesystem that is managed by Docker (/var/lib/docker/volumes/ on Linux).
The following docker-compose.yaml uses a volume for Redis:
services:
redis:
image: "redis:alpine"
volumes:
- redis-data:/data
volumes:
redis-data:Explanation:
- Volumes: Maps a Docker-managed volume named
redis-datato the/datadirectory inside the Redis container. - Persistent Storage: Data stored in
redis-datapersists even if the Redis container is removed or recreated.
Explore additional Docker Compose commands to manage your services effectively.
-
Run Services in Detached Mode:
To run your services in the background, use the
-dflag:docker compose up -d
Output Example:
Starting composetest_redis_1... Starting composetest_web_1... -
List Running Containers:
View currently running services with:
docker-compose ps
-
View Help:
To see other available commands, execute:
docker-compose --help
-
Stop Services:
-
Option 1: Stop services without removing containers:
docker compose stop
-
Option 2: Remove containers entirely:
docker compose down
-
Option 3: Remove containers, network and volumes. This is useful when you want to completely clean up your Docker environment.
docker compose down -v
-
Scaling your application to handle increased load or to ensure high availability involves running multiple instances (replicas) of your services.
Modify the docker-compose service to run three instances of the Flask app:
docker-compose.yaml:
services:
web:
build: .
ports:
- "8000:5000"
web2:
build: .
ports:
- "8001:5000"
web3:
build: .
ports:
- "8002:5000"
redis:
image: "redis:alpine"
container_name: redis_container
volumes:
- ./redis_data:/dataRe-start your docker-compose services:
docker compose upAccess the application at http://localhost:8000/, http://localhost:8001/, and http://localhost:8002/.
Note:
Each instance of the Flask app will have a unique hostname and IP address. Refresh the page to see the hit counter increment across different instances.
Create a new branch "task3" and commit your changes. Push the changes to the remote repository.
git checkout -b task3
git add .
git commit -m "Docker Compose for multi-service Python web application"
git push origin task3