Containerize a Flask application

9 minute read

Containerize a Flask application

The following topics are covered: Benefits of containerizing, why choose containers over VM, Docker basics, install postgres from Dockerhub and finally build and run a flask app in docker.

Why containerize?

Containers solve the problem of environment consistency when you deploy your application from a development environment to a production environment.

Two ways of bundling an application with its environment and dependencies are containers and virtual machines. Both methods allow an application to be run with minimal differences between development and production, and both can be used to horizontally scale an application.

Multiple containers and VMs can be run on the same machine, but containers running on the same machine share the same low-level operating system kernel.

Containers vs VMs

Earlier it was mentioned that containers are a light-weight option to bundle an application with its environment. Now we will learn more about the differences between containers and VMs.

A VM is like a complete computer, with its own copy of an operating system and virtual hardware. Just as with containers, a single physical machine (the host machine) can run many virtual machines to scale the number of isolated applications. While virtual machines work well for scaling applications, since they virtualize an entire machine, they can be resource intensive. This is where containers can be an improvement.

Instead of virtualizing an entire operating system and hardware, containers package and run programs on a single host operating system by sharing the operating system kernel and partitioning the operating system’s resources.

In the container model, there are no virtual operating systems or virtual hardware, which reduces the total resources needed to run them.

Some popular Virtual Machine platforms are VMWare, VirtualBox, and Parallels Desktop.

Some common container platforms include Rkt, LXC and LXD, OpenVz, and Docker. Docker is the most popular container platform and is synonymous to containers.

Docker - an open source container system

Once you install Docker on your Mac, you will have the docker engine on your machine.

Docker Engine is an application that consists of a daemon, an API, and a client:

  • The Docker daemon is a server that manages the images, containers, networks, and volumes.
  • The Docker client is the user interface for Docker. The client is a CLI, so most of the work you do with Docker will take place at the command line.

The client communicates with the daemon through the command line API as shown in the image below:

You will be using the Docker Engine to create and run containers, so if you have not installed Docker using the links above, please be sure to do so.

Additional parts of the Docker platform you will interact with are the Docker image, container, and registry.

Docker Image

A Docker image is the set of instructions for creating a container.

The image typically includes a file system and the parameters that will be used for the container.

Images are comprised of multiple layers. Each layer contains specific software.

You can create a custom Docker image for a specific application. Start with a standardized parent image as the base layer. The parent image often contains the file system of a particular operating system, such as Ubuntu 18.04. Then add an additional layer to the image, on top of the base layer. You can add your application files to this additional layer. You can even add multiple additional layers, and distribute your application files across different layers, as appropriate for your needs.

Docker container

You have already been introduced to containers, and a Docker container is just the Docker-specific implementation of the concept. In practice, Docker containers are created from Docker images - a container is a runnable instance of an image. Note that since the image is a set of instructions for creating a container, multiple containers can be created from the same image.

Docker registry

Docker images can be stored and distributed using a Docker registry. In the next section, we will download and run an image from DockerHub, which is a free registry with many images we can use.

Example: Install postgres from Dockerhub

DockerHub is the world’s largest registry of Docker images with more than 100,000 images available. DockerHub is the default registry for Docker. It contains images ready to run a great variety of applications. It is maintained by the Docker company.

Dockerhub has images for all major Linux distributions, as well as images designed for a specific software. There are python images, images for running Flask, images for running databases such as Postgres or MySQL and so on. For this example, let’s pull down and load an image that is pre-built to make Postgres run.

  1. Find the Postgres image by searching on DockerHub. Postgres is an open source relational database, and the Postgres Docker image has the database software installed.

  2. On the command line tell docker to download the image to your machine:

(base) shravan-docker# docker pull postgres:latest
latest: Pulling from library/postgres
852e50cd189d: Pull complete
0269cd569193: Pull complete
879ff0b54097: Pull complete
3114d3793d45: Pull complete
0ee7737137c3: Pull complete
3fff8dbf0e49: Pull complete
6b78b1e21dd1: Pull complete
ddecce665bec: Pull complete
3fae122f28bd: Pull complete
69289a4f935b: Pull complete
56d6c36d6958: Pull complete
d794b9ea73a0: Pull complete
1738fda53f17: Pull complete
bbcdf7c12dbd: Pull complete
Digest: sha256:839d6212e7aadb9612fd216374279b72f494c9c4ec517b8e98d768ac9dd74a15
Status: Downloaded newer image for postgres:latest
docker.io/library/postgres:latest
(base) shravan-docker#
  1. Run the image using the docker run command docker run --name psql -e POSTGRES_PASSWORD=password! -p 5432:5432 -d postgres:latest, supplying a password for Postgres:
(base) shravan-docker# docker run --name psql -e POSTGRES_PASSWORD=password! -p 5432:5432 -d postgres:latest
8b062f53e43cdf2e474954e220f3d2535e7e72ddc88fb086a9354cad78af0f6a
(base) shravan-docker#

  1. Install the postgres client.

  2. Once you have the Postgres client installed, you can use it to connect with the Docker container database using the following command: psql -h 0.0.0.0 -p 5432 -U postgres. This command allows you to access the database using the same port that you exposed earlier.

(base) shravan-docker# psql -h 0.0.0.0 -p 5432 -U postgres
Password for user postgres:
psql (12.4, server 13.1 (Debian 13.1-1.pgdg100+1))
WARNING: psql major version 12, server major version 13.
         Some psql features might not work.
Type "help" for help.

postgres=# \dt
Did not find any relations.
postgres=#
  1. Test the container with some SQL commands. For example, you can list all databases using \l command or list all tables using the \dt command. More commands can be found in the Postgres documentation.
postgres=# \dt
Did not find any relations.
postgres=# \l
                                 List of databases
   Name    |  Owner   | Encoding |  Collate   |   Ctype    |   Access privileges
-----------+----------+----------+------------+------------+-----------------------
 postgres  | postgres | UTF8     | en_US.utf8 | en_US.utf8 |
 template0 | postgres | UTF8     | en_US.utf8 | en_US.utf8 | =c/postgres          +
           |          |          |            |            | postgres=CTc/postgres
 template1 | postgres | UTF8     | en_US.utf8 | en_US.utf8 | =c/postgres          +
           |          |          |            |            | postgres=CTc/postgres
(3 rows)

postgres=#
  1. When you are finished testing Postgres, you can quit your connection to postgres using \q

  2. To stop the running Docker container, you will first need to find it. You can list the running containers with the command docker ps. Copy the id of your postgres container, and use docker stop <container-id>

(base) shravan-docker# docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                    NAMES
8b062f53e43c        postgres:latest     "docker-entrypoint.s…"   6 minutes ago       Up 6 minutes        0.0.0.0:5432->5432/tcp   psql
(base) shravan-docker# docker stop 8b062f53e43c
8b062f53e43c
(base) shravan-docker# docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
(base) shravan-docker#

A word about Dockerfiles

Dockerfiles are text files used to define Docker images. They contain commands used to define a source or parent image, copy files to the image, install software on the image, and define the application which will run when the image is invoked.

a Dockerfile typically starts with a source image upon which you can add layers to build your own custom image. For example, the Dockerfile started with the source image python:3.7.2-slim:

FROM python:3.7.2-slim

COPY . /app
WORKDIR /app

RUN pip install --upgrade pip
RUN pip install flask


ENTRYPOINT [“python”, “app.py”]

Additional layers in the Dockerfile can be used to install dependencies, like flask in the Dockerfile above. They can also be used to setup a working directory and define an entrypoint for your container. In the example above, the executable is app.py, which will be run when the container starts.

The image below summarizes the process of running a container starting from a Dockerfile:

The example above introduced us to several common Dockerfile commands that can be used when creating your own Dockerfiles:

  • Dockerfile comments start with #.
  • FROM defines source image upon which the image will be based.
  • COPY copies files to the image.
  • WORKDIR defines the working directory for other commands.
  • RUN is used to run commands other than the main executable.
  • ENTRYPOINT is used to define the main executable.

Gunicorn is a lightweight server used to run web applications. It is a production-ready solution that can be used to run a Flask app using the command gunicorn -b :8080 main:APP. The command we use to specify the gunicorn command as the main executable is ENTRYPOINT["gunicorn", "-b", ":8080", "main:APP"]. The ENTRYPOINT specifies the main executable for the image.

Build a flask image and run the container locally

Build the image from the same directory using the command docker build --tag test .

(base) shravan-flask# pwd
/Users/shravan/projects/flask-app-on-aws-eks/examples/flask
(base) shravan-flask# ls -rtl
total 16
-rw-r--r--  1 shravan  staff  137 Dec  5 16:17 Dockerfile
-rw-r--r--  1 shravan  staff  204 Dec  5 16:17 app.py
(base) shravan-flask# docker build --tag test .
Sending build context to Docker daemon  3.072kB
Step 1/6 : FROM python:3.7.2-slim
3.7.2-slim: Pulling from library/python
27833a3ba0a5: Pull complete
ad5023f26108: Pull complete
35cace2fde21: Pull complete
89a6b3b8082f: Pull complete
88e577cb069e: Pull complete
Digest: sha256:78320634b63efb52f591a7d69d5a50076ce76e7b72c4b45c1e4ddad90c39870a
Status: Downloaded newer image for python:3.7.2-slim
 ---> f46a51a4d255
Step 2/6 : COPY . /app
 ---> feedce40422e
Step 3/6 : WORKDIR /app
 ---> Running in c248c43879d3
Removing intermediate container c248c43879d3
 ---> b4238e0725fd
Step 4/6 : RUN pip install --upgrade pip
 ---> Running in 28e7a121e296
Collecting pip
  Downloading https://files.pythonhosted.org/packages/ab/11/2dc62c5263d9eb322f2f028f7b56cd9d096bb8988fcf82d65fa2e4057afe/pip-20.3.1-py2.py3-none-any.whl (1.5MB)
Installing collected packages: pip
  Found existing installation: pip 19.0.3
    Uninstalling pip-19.0.3:
      Successfully uninstalled pip-19.0.3
Successfully installed pip-20.3.1
Removing intermediate container 28e7a121e296
 ---> 43da6f4ab65a
Step 5/6 : RUN pip install flask
 ---> Running in dab624dcc368
Collecting flask
  Downloading Flask-1.1.2-py2.py3-none-any.whl (94 kB)
Collecting click>=5.1
  Downloading click-7.1.2-py2.py3-none-any.whl (82 kB)
Collecting itsdangerous>=0.24
  Downloading itsdangerous-1.1.0-py2.py3-none-any.whl (16 kB)
Collecting Jinja2>=2.10.1
  Downloading Jinja2-2.11.2-py2.py3-none-any.whl (125 kB)
Collecting MarkupSafe>=0.23
  Downloading MarkupSafe-1.1.1-cp37-cp37m-manylinux1_x86_64.whl (27 kB)
Collecting Werkzeug>=0.15
  Downloading Werkzeug-1.0.1-py2.py3-none-any.whl (298 kB)
Installing collected packages: MarkupSafe, Werkzeug, Jinja2, itsdangerous, click, flask
Successfully installed Jinja2-2.11.2 MarkupSafe-1.1.1 Werkzeug-1.0.1 click-7.1.2 flask-1.1.2 itsdangerous-1.1.0
Removing intermediate container dab624dcc368
 ---> fb61fadf9341
Step 6/6 : ENTRYPOINT ["python", "app.py"]
 ---> Running in 3632af1caac0
Removing intermediate container 3632af1caac0
 ---> da819164c044
Successfully built da819164c044
Successfully tagged test:latest
(base) shravan-flask#

Once the image is built, you can run the container with the command: docker run -p 80:8080 test. Here, we are binding port 8080 of the container to the port 80 of your local machine.

(base) shravan-flask# docker run  -p 80:8080 test
 * Serving Flask app "app" (lazy loading)
 * Environment: production
   WARNING: This is a development server. Do not use it in a production deployment.
   Use a production WSGI server instead.
 * Debug mode: on
 * Running on http://0.0.0.0:8080/ (Press CTRL+C to quit)
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: 996-916-414
172.17.0.1 - - [05/Dec 21:41:45] "GET / HTTP/1.1" 200 -

Stop the container, either using control-c or docker stop <container-id>

Summary

  • Learned about containers and the Docker container system
  • Learned about the difference between containers and traditional virtual machines
  • Pulled an image from DockerHub
  • Learned about Dockerfiles and the basic commands needed to write them
  • Built and ran containers from Dockerfiles

Tags:

Updated: