Containerize a Flask application
The following topics are covered: Benefits of containerizing, why choose containers over VM, Docker basics, install
Dockerhub and finally build and run a flask app in docker.
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
Some common container platforms include
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
- 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
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.
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 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.
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.
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#
- 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#
Install the postgres client.
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=#
- Test the container with some SQL commands. For example, you can list all databases using
\lcommand or list all tables using the
\dtcommand. 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=#
When you are finished testing Postgres, you can quit your connection to postgres using
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 #.
FROMdefines source image upon which the image will be based.
COPYcopies files to the image.
WORKDIRdefines the working directory for other commands.
RUNis used to run commands other than the main executable.
ENTRYPOINTis used to define the main executable.
Gunicornis a lightweight server used to run web applications. It is a production-ready solution that can be used to run a
Flaskapp 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
ENTRYPOINTspecifies 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
docker stop <container-id>
- 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