Containerization
Workshop

Tips for navigating the slides:
  • Press O or Escape for overview mode.
  • Visit this link for a nice printable version
  • Press the copy icon on the upper right of code blocks to copy the code

Welcome!

Classroom "rules":

  • I am here for you!
  • Every question is important
  • Help each other

Introductions

Tell us about yourself:

  • Name
  • Pronouns
  • Location
  • Programming/Web experience
  • What interests you about containerization?
  • What is the coolest/weirdest/cutest animal? 🐍

Today's agenda

Bit (the raccoon) lecturing
  • Intro to Containers
  • Containerizing Flask
  • 👩🏾‍💻 Exercise: Run a container
  • Databases in containers
  • 👩🏼‍💻 Exercise: Run multiple containers
  • Deploying a container
  • 👩🏻‍💻 Exercise: Deploy a container app

Prerequisites

Option 1: Online development with Codespaces:

Option 2: Local development with VS Code

Option 3: Local development

Docker

Docker overview

The Docker engine runs multiple Docker containers, where each container is an isolated environment.

Diagram of two Docker containers running on top of one Docker engine running on top of an Operating System on top of Hardware

Docker overview example

Each container can be a very different environment, with binaries and libraries dependent on the application.

Diagram of two Docker containers, one with Python/PostgreSQL/Django, the other with Ruby/MySQL/Rails

Why Docker?

  • Environment consistency: Ensure that the developer environment, test environment, staging environment, and production environments are the same.
  • Application portability: Easy to run the application on new hardware if old hardware fails or if application needs to scale.
  • Efficient hardware use: A machine can run multiple containers to make optimal use of its resources.

Docker images

A container image is a software package that includes everything needed to run an application.
A container is a running instance of a container image.

Diagram of two Docker images, one with Python/PostgreSQL/Django, the other with Ruby/MySQL/Rails, and one running Django container

Docker images

Multiple containers can be run from the same image.

Diagram of two Django containers running on top of a single Docker engine

Image registries

A registry is a place to store and share images.

Commonly used image registries:

Image layers

A container image often starts off with a base image, and then adds layers on top of it.

For example:

  • Base image: Ubuntu 20.04
  • Layer 1: Python 3.9
  • Layer 2: Flask 2.0
  • Layer 3: Your app

Docker can cache each layer, which improves performance.

Productionizing
Flask apps

Bit (the raccoon) in front of a computer and Python logo

Sample Flask app Open in GitHub Codespaces


                from flask import Flask, render_template, request

                app = Flask(__name__, template_folder='templates', static_folder='static')
                
                @app.route('/')
                def index():
                   return render_template('index.html')
            
                @app.route('/hello')
                def hello():
                    return render_template('hello.html', name=request.args.get('name'))
                

👀 Demo: flaskcontainer-jd5m-containerapp.graymoss-ba9f7d1d.centralus.azurecontainerapps.io

👩🏼‍💻 Code: github.com/pamelafox/simple-flask-server-container

Running Flask app locally

Using the built-in Flask server:


                python3 -m flask run --port 50505 --debug
                

⚠️ The dev server is not recommended for production use.

Running Flask with gunicorn

Gunicorn is a production-level server that can run multiple worker processes.

Add gunicorn to requirements.txt:


                Flask==2.2.3
                gunicorn==20.1.0
                

Use gunicorn to run Flask app with multiple workers:


                python3 -m gunicorn app:app --workers 4 --bind 0.0.0.0:50505
                

Configuring gunicorn

Gunicorn can be configured with a gunicorn.conf.py file:


                import multiprocessing
                    
                max_requests = 1000
                max_requests_jitter = 50
                log_file = "-"
                bind = "0.0.0.0:50505"
                
                workers = (multiprocessing.cpu_count() * 2) + 1
                threads = workers
                timeout = 120
                

The run command can be simplified to:


                python3 -m gunicorn app:app
                

Containerizing
Python apps

Bit (the raccoon) as Neo from the Matrix

Containerization steps

  1. Write a Dockerfile
  2. Build image from Dockerfile
  3. Run container using built image

Dockerfile format

A Dockerfile includes:

The base or parent image* FROM python:3.11
Additional software RUN pip3 install Flask gunicorn
Application code WORKDIR /code
COPY . .
Services to expose (storage/network) EXPOSE 50505
Command to run upon launching container ENTRYPOINT ["gunicorn", "-c", "gunicorn.conf.py", "app:app"]

*Find existing images in registries, like DockerHub.

Dockerfile for Flask

A complete file:


                FROM python:3.11

                WORKDIR /code

                COPY requirements.txt .
                RUN pip3 install -r requirements.txt

                COPY . .

                EXPOSE 50505

                ENTRYPOINT ["gunicorn", "-c", "gunicorn.conf.py", "app:app"]
                

📖 Learn more: Docker images layer and cache

Add a dockerignore file

Prevent unnecessary files from being copied to the image:


                .git*
                .venv/
                **/*.pyc
                __pycache__/
                

Building the image

Using the docker build command:


                docker build --tag flaskapp .
                

Using the VS Code Docker extension:

Screenshot of Docker extension in Visual Studio Code

Running the container

Using the docker run command:


                docker run --publish 50505:50505 flaskapp
                

Using Docker Desktop:

Screenshot of Docker images list with run button next to one of them

You can also use the VS Code Docker extension to run containers.

Exercise: Run a container

Starting from this repo:
github.com/pamelafox/simple-flask-server-container

  1. Follow the Local development with Docker steps.
  2. Try changing the base image to a higher Python version number and re-building / re-running.
  3. Try changing the HTML code and re-building / re-running.

🙋🏼‍♀️🙋🏾‍♀️🙋🏽‍♀️ Let us know if you need any help! 🙋🏻‍♀️🙋🏽‍♂️🙋🏿‍♀️

Databases in containers

Sample Flask app with DB Open in GitHub Codespaces


                ...
                @app.route('/surveys', methods=['GET'])
                def surveys_list_page():
                    surveys = Survey.query.all()    
                    return render_template('surveys_list.html', surveys=surveys)
                
                @app.route('/surveys/', methods=['GET'])
                def survey_page(survey_id):
                    survey = Survey.query.where(Survey.id == survey_id).first()
                    answers = Survey.query.where(Answer.survey==survey_id)
                    return render_template('survey_details.html', survey=survey, answers=answers, already_voted='survey_id' in request.cookies)
                ...
                

👀 Demo: flasurveycon-b2ikkm-ca.ashysea-c408c9d0.eastus.azurecontainerapps.io/surveys

👩🏼‍💻 Code: github.com/pamelafox/flask-surveys-container-app

Data persistence in containers

Data can be written to a container's file system, but:

  • Removing a container removes the data
  • Container data is difficult to move between environments
  • Container storage drives are less performant

If you need to persist data, you should store it outside the container.

Docker volumes

A volume is a directory on the host machine that is mapped to a directory in the container.

Diagram of Docker container and Docker volumes inside the File System

When developing with databases locally, use a volume to store the data for the database.

Running PostgreSQL with Docker

Create a volume:


                docker volume create postgres-data
                

Create a network for the containers to communicate over:


                docker network create postgres-net
                

Run a PostgreSQL container with the volume and network:


                docker run --rm -d -v postgres-data:/var/lib/postgresql/data \
                    --network postgres-net --name db \
                    -e POSTGRES_USER=app_user -e POSTGRES_PASSWORD=app_password \
                    postgres
                

From Docker tutorial: Run a database in a container

Connecting the app to the DB

Set environment variables for the database connection:


                DBHOST=db
                DBNAME=postgres
                DBUSER=app_user
                DBPASS=app_password
                

Build the container:


                docker build --tag flasksurveyscontainerapp .
                

Run the app container over the same network:


                docker run --rm -d --network postgres-net \
                    --name flask-db-app -p 50505:50505 \
                    flasksurveyscontainerapp
                

Docker compose

Docker compose is a tool for defining and running multi-container Docker apps, and docker-compose.yaml is a YAML file that defines the services that make up your app.


                services:
                    db:
                        image: postgres
                        restart: always
                        environment:
                            POSTGRES_PASSWORD: ${DBPASS:?database password not set}
                            POSTGRES_USER: ${DBUSER:?database user not set}
                            POSTGRES_DB: ${DBNAME:?database name not set}
                        volumes:
                            - postgres-data:/var/lib/postgresql/data
                        healthcheck:
                            test: ["CMD-SHELL", "pg_isready -U ${DBUSER} -d ${DBNAME}"]
                            interval: 5s
                            timeout: 5s
                            retries: 5
                    app:
                        build:
                            context: .
                        ports:
                            - 50505:50505
                        depends_on:
                            db:
                                condition: service_healthy
                    volumes:
                        postgres-data:
                

Run multiple containers

Run the app and database containers:


                docker-compose up
                

Exercise: Run multiple containers

Starting from this repo:
github.com/pamelafox/flask-surveys-container-app

  1. Follow the Local development with Docker steps.
  2. Make a survey in the app.
  3. If you're in Codespaces, change the visibility of port 50505 to "Public" and share the URL of your survey with classmates.

🙋🏼‍♀️🙋🏾‍♀️🙋🏽‍♀️ Let us know if you need any help! 🙋🏻‍♀️🙋🏽‍♂️🙋🏿‍♀️

Hosting containers
on Azure!

Bit (the raccoon) in the clouds next to Azure logo

Hosting considerations

  • How much traffic do you expect?
  • Do you need scale-to-zero?
  • How variable will the traffic be?
  • How much control do you need over the environment?

Azure hosting options

Cloud Azure
Environment Containers PaaS
Azure Kubernetes Service Container Management Azure App Service Serverless
Azure Container Apps Azure Functions

For hosting containers, Kubernetes Service, Container Apps, and App Service are all good options.

Storage in container apps

For temporary storage, you can write to file system or have an ephemeral volume in a container app.

For permanent storage, you can mount Azure Files but performance is too limited to be useful for a database.

Best approach for Azure:
Use a managed database service outside the container.

Azure managed databases services

These are just some of the options:

Option Description
Azure CosmosDB Distributed database with multiple APIs, including MongoDB and Cassandra.
Azure Cosmos DB for PostgreSQL Distributed database using PostgreSQL and the Citus extension. Can scale vertically and horizontally.
Azure Database for PostgreSQL – Flexible Server Fully managed service with vertical scaling.

Hosting on Azure Container Apps

A Container Apps Environment manages a Container App which pulls its image from an Azure Container Registry.

The Container App connects with the PostgreSQL server over the internal Azure network.

Flask container architecture diagram: Azure Container Apps Environment, Azure Container App, Azure Container Registry, Container, and PostgreSQL Server

Deploying to Azure Container Apps

Using the Azure Dev CLI:

Screenshot of azd up command deploying to Azure Container Apps

Exercise: Deploy a container app

Starting from this repo (or your fork):
github.com/pamelafox/flask-surveys-container-app

  1. Sign up for a free Azure account and create a subscription.
  2. Either open the project in Codespaces or follow these installation steps for the Azure Developer CLI.
  3. Follow the Deployment steps in the README.
  4. If it deploys successfully, share the endpoint URL with your classmates. If not, let us know about any bugs. 🪲
  5. Once you've verified the app is working, run azd down to un-deploy the app.

🙋🏼‍♀️🙋🏾‍♀️🙋🏽‍♀️ Let us know if you need any help! 🙋🏻‍♀️🙋🏽‍♂️🙋🏿‍♀️

More resources

Raccoons with laptops

Any questions?

A bunch of raccoon students with computers