Python > Deployment and Distribution > Containerization > Docker Compose for Multi-Container Applications
Docker Compose for a Simple Web Application with Redis
This example demonstrates deploying a basic web application along with a Redis instance using Docker Compose. The web application will increment a counter stored in Redis on each page load.
Project Structure
Before diving into the `docker-compose.yml` file, it's important to understand the project structure. This example assumes you have the following files: - `docker-compose.yml`: The Docker Compose file that defines the services. - `app.py`: The Python Flask application code. - `requirements.txt`: Lists the Python dependencies for the Flask application. - `Dockerfile`: Defines how to build the web application's Docker image.
Dockerfile (web application)
This Dockerfile sets up the environment for the web application. It starts from a Python 3.9 slim image, installs the necessary dependencies from `requirements.txt`, copies the `app.py` file, exposes port 5000, and finally runs the Flask application.
FROM python:3.9-slim-buster
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY app.py .
EXPOSE 5000
CMD ["python", "app.py"]
requirements.txt (web application)
This file lists the Python packages that need to be installed for the Flask application: Flask itself, and the Redis Python client.
Flask
redis
app.py (web application)
This Flask application connects to Redis, increments a counter named 'hits' on each request, and displays the current count. It retrieves the Redis host and port from environment variables, allowing Docker Compose to configure the connection details. If the environment variables are not defined it will use defaults values 'redis' as the host and 6379 as port.
from flask import Flask
import redis
import os
app = Flask(__name__)
redis_host = os.environ.get('REDIS_HOST', 'redis')
redis_port = int(os.environ.get('REDIS_PORT', 6379))
redis_client = redis.Redis(host=redis_host, port=redis_port)
@app.route('/')
def hello():
redis_client.incr('hits')
return 'Hello! This page has been visited {} times.\n'.format(redis_client.get('hits').decode('utf-8'))
if __name__ == '__main__':
app.run(debug=True, host='0.0.0.0')
docker-compose.yml
This `docker-compose.yml` file defines two services: `web` and `redis`. - **web:** This service builds the web application image using the `Dockerfile` in the current directory (`.`). It maps port 5000 on the host to port 5000 in the container. The `depends_on` directive ensures that the Redis service is started before the web application. It also sets the `REDIS_HOST` environment variable for the web application, allowing it to connect to the Redis service using its service name. - **redis:** This service uses the official Redis image from Docker Hub. It maps port 6379 on the host to port 6379 in the container.
version: "3.9"
services:
web:
build: .
ports:
- "5000:5000"
depends_on:
- redis
environment:
REDIS_HOST: redis
redis:
image: redis:latest
ports:
- "6379:6379"
Running the application
To run the application, navigate to the directory containing the `docker-compose.yml` file and run the command `docker-compose up`. This will build the web application image, start the Redis container, and then start the web application container. You can then access the web application in your browser at `http://localhost:5000`. Each time you refresh the page, the counter will increment.
Concepts Behind the Snippet
This snippet demonstrates several key concepts: - **Containerization:** Packaging the application and its dependencies into a Docker container for portability and reproducibility. - **Service Definition:** Defining the different components of the application (web application and Redis) as services in the `docker-compose.yml` file. - **Dependency Management:** Using `depends_on` to ensure that services are started in the correct order. - **Networking:** Docker Compose automatically creates a network that allows the services to communicate with each other using their service names. - **Environment Variables:** Using environment variables to configure the application, making it more flexible and adaptable to different environments.
Real-Life Use Case
This example can be extended to more complex applications, such as: - Microservices architecture where different services communicate with each other through APIs. - Web applications that use a database for persistent data storage. - Data processing pipelines that involve multiple stages of data transformation. - Queuing systems where tasks are submitted to a queue and processed by worker services.
Best Practices
- Use official Docker images whenever possible. - Define the application dependencies clearly in `requirements.txt` or a similar file. - Use environment variables for configuration. - Use a `.dockerignore` file to exclude unnecessary files from the Docker image. - Use Docker Compose to manage multi-container applications. - Always define a specific tag for the images that your applications are based upon. For example, instead of using redis:latest, use redis:7.2.
Interview Tip
Be prepared to explain the benefits of using Docker and Docker Compose, such as portability, reproducibility, and ease of deployment. Also, be prepared to discuss the different sections of the `docker-compose.yml` file and how they relate to each other. Understand the importance of `depends_on` and how it ensures that services are started in the correct order. Also be prepare to explain the basics of networking in Docker Compose.
When to Use Them
Use Docker Compose when you have a multi-container application that needs to be deployed and managed as a single unit. It simplifies the process of defining and running complex applications with multiple dependencies.
Memory Footprint
The memory footprint of the application will depend on the size of the Docker images and the amount of memory used by the application and its dependencies. Using slim images and optimizing the application code can help reduce the memory footprint.
Alternatives
Alternatives to Docker Compose include: - **Kubernetes:** A more complex container orchestration platform that is suitable for large-scale deployments. - **Docker Swarm:** Docker's built-in container orchestration solution. - **Ansible:** An automation tool that can be used to deploy and manage applications.
Pros
- Simplifies the deployment and management of multi-container applications. - Provides a declarative way to define the application infrastructure. - Makes it easy to reproduce the application environment. - Improves portability and consistency.
Cons
- Can be complex to set up and configure for large-scale deployments. - Requires a good understanding of Docker and containerization concepts. - May not be suitable for all types of applications.
FAQ
-
What is the difference between `docker build` and `docker-compose build`?
`docker build` builds a single Docker image from a Dockerfile. `docker-compose build` builds multiple Docker images, one for each service defined in the `docker-compose.yml` file. It streamlines the process of building images for a multi-container application. -
How do I scale a service using Docker Compose?
You can scale a service using the `docker-compose scale` command. For example, to scale the `web` service to 3 instances, you would run `docker-compose scale web=3`. -
How do I stop and remove the containers created by Docker Compose?
You can stop and remove the containers using the `docker-compose down` command. This will stop the containers, remove them, and remove the network that was created by Docker Compose.