Dockerize Multi-Container Python App Using Compose

Learn how to containerize Python Application Using Docker Compose

The app fetches the quote of the day from a public API hosted at http://quotes.rest then it caches the result in Redis. For subsequent API calls, the app will return the result from Redis cache instead of fetching it from the public API

create following file structure :

python-docker-compose
   ↳ app.py


from flask import Flask
from datetime import datetime
import requests
import redis
import os
from dotenv import load_dotenv
import json

load_dotenv()  # take environment variables from .env.


app = Flask("app")

def get_quote_from_api():
	API_URL = "http://quotes.rest/qod.json"
	resp = requests.get(API_URL)
	if resp.status_code == 200:
		try:
			quote_resp = resp.json()["contents"]["quotes"][0]["quote"]
			return quote_resp
		except (KeyError, IndexError) as e:
			print (e)
			return None
	else:
		return None


@app.route("/")
def index():
	return "Welcome! Please hit the `/qod` API to get the quote of the day."


@app.route("/qod")
def quote_of_the_day():
	# get today's date in string
	date = datetime.now().strftime("%Y-%m-%d")
	quote = redis_client.get("date")
	if not quote:
		quote = get_quote_from_api()	
	return "Quote of the day: " + quote




if __name__ == '__main__':
	# Connect to redis client
	redis_host = os.environ.get("REDIS_HOST", "localhost")
	redis_port = os.environ.get("REDIS_PORT", 6379)
	redis_password = os.environ.get("REDIS_PASSWORD", None)
	redis_client = redis.StrictRedis(host=redis_host, port=redis_port, password=redis_password)

	# Run the app
	app.run(port=8080, host="0.0.0.0")



Run python application

git clone https://github.com/docker-community-leaders/dockercommunity/
cd /content/en/examples/Python/python-docker-compose
pip install -r requirements.txt
python app.py

On a different terminal run

$ curl http://localhost:8080/qod
The free soul is rare, but you know it when you see it - basically because you feel good, very good, when you are near or with them.

Dockerize above Python application

# Dockerfile References: https://docs.docker.com/engine/reference/builder/

# Start from python:3.8-alpine base image
FROM python:3.8-alpine

# The latest alpine images don't have some tools like (`git` and `bash`).
# Adding git, bash and openssh to the image
RUN apk update && apk upgrade && \
    apk add --no-cache bash git openssh

# Make dir app
RUN mkdir /app
WORKDIR /app
COPY requirements.txt requirements.txt

RUN pip install -r requirements.txt

# Copy the source from the current directory to the Working Directory inside the container
COPY . .


COPY . .

# Expose port 8080 to the outside world
EXPOSE 8080

# Run the executable
CMD ["python", "app.py"]

Application services via Docker Compose

Our application consists of two services -

  • App service that contains the API to display the “quote of the day”.
  • Redis which is used by the app to cache the “quote of the day”.

Let’s define both the services in a docker-compose.yml file

# Docker Compose file Reference (https://docs.docker.com/compose/compose-file/)

version: '3'

# Define services
services:

  # App Service
  app:
    # Configuration for building the docker image for the service
    build:
      context: . # Use an image built from the specified dockerfile in the current directory.
      dockerfile: Dockerfile
    ports:
      - "8080:8080" # Forward the exposed port 8080 on the container to port 8080 on the host machine
    restart: unless-stopped
    depends_on: 
      - redis # This service depends on redis. Start that first.
    environment: # Pass environment variables to the service
      REDIS_HOST: redis
      REDIS_PORT: 6379    
    networks: # Networks to join (Services on the same network can communicate with each other using their name)
      - backend

  # Redis Service   
  redis:
    image: "redis:alpine" # Use a public Redis image to build the redis service    
    restart: unless-stopped
    networks:
      - backend

networks:
  backend:    

Running the application with docker compose

$ docker-compose up
Starting python-docker-compose_redis_1 ... done
Starting python-docker-compose_app_1   ... done
Attaching to python-docker-compose_redis_1, python-docker-compose_app_1
redis_1  | 1:C 02 Feb 2019 12:32:45.791 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
redis_1  | 1:C 02 Feb 2019 12:32:45.791 # Redis version=5.0.3, bits=64, commit=00000000, modified=0, pid=1, just started
redis_1  | 1:C 02 Feb 2019 12:32:45.791 # Warning: no config file specified, using the default config. In order to specify a config file use redis-server /path/to/redis.conf
redis_1  | 1:M 02 Feb 2019 12:32:45.792 * Running mode=standalone, port=6379.
redis_1  | 1:M 02 Feb 2019 12:32:45.792 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
redis_1  | 1:M 02 Feb 2019 12:32:45.792 # Server initialized
redis_1  | 1:M 02 Feb 2019 12:32:45.792 # WARNING you have Transparent Huge Pages (THP) support enabled in your kernel. This will create latency and memory usage issues with Redis. To fix this issue run the command 'echo never > /sys/kernel/mm/transparent_hugepage/enabled' as root, and add it to your /etc/rc.local in order to retain the setting after a reboot. Redis must be restarted after THP is disabled.
redis_1  | 1:M 02 Feb 2019 12:32:45.793 * DB loaded from disk: 0.000 seconds
redis_1  | 1:M 02 Feb 2019 12:32:45.793 * Ready to accept connections
app_1    | 2019/02/02 12:32:46 Starting Server

The docker-compose up command starts all the services defined in the docker-compose.yml file. You can interact with the python service using curl -

$ curl http://localhost:8080/qod
A show of confidence raises the bar

Stopping all the services with docker compose

$ docker-compose down



Last modified 10.10.242410: Update config.toml (2e6675a)