Build Python Web Application using Flask and Docker
By: Ridwan Fajar
Tags: Docker, Container Orchestration, Python, Docker Compose, Redis

Foreword

As far as I understand, Docker is not so popular as today at 2014. System Administrator (I didn’t call the role as DevOps Engineer), try to pull new version of backend application by versioning control system only. So once they have multiple machines, they should access their servers one by one and pull the new version into the servers. One of the real problem was when you have to rollback the version, you should repeat those workflow once again upon multiple machines. You might be bored to do the same things just to rollback and deploy new version of your backend application.

The things went different once orchestration system such as Kubernetes become dominator to spread Docker adoption within enterprise. There are some approach such as “microservice” that let you split up your monolithic web application into a lot of services that interact each other.

I used docker mostly in my daily basis once I joined Sensehealth B.V. at 2017. This company run online mental healthcare service at Netherland. I learned a lot here how to use Docker itself to containerize Django application, using Docker Compose for local development and orchestrate our web application in production level by using Rancher v1.0 that using Cattle as orchestration engine.

Docker is not the pioneer to containerize your web application. There were so many tools that might be used widely by enterprises at that time [1]. But of course, huge support from community and enterprises made this thing become bigger and popular.

As I said on the first paragraph, Docker might be suitable if you want to manage your web application inside cluster machines to scale up your service capability instead just using one node. It’s more better than you are just using Ansible to deploy your web application configuration. You only need to build you web application and distributed it in all over nodes you manage.

Ok, that’s enough for the foreword. Let’s move to the next section.

A. Writing the web application using Flask and Redis-py

In this article, I want to give you picture about building our web application as Docker container. Before that, we have to write some web application that might have an interaction with the client. I have an example of web application which is written in Python, Flask and Redis-py.

This web application has a feature to accept visitor of our websites and reset the visitor number that might we have in the future. The other simple endpoint is “Hello World”.

I use Redis as a simple approach how to build web application that store some of their data from the client. Redis might be configured as persistent key-value store. But in this tutorial we won’t have that. As long Redis is running, client data will be persist temporarily.

Here is the snippet of main.py:

import redis from flask import Flask app = Flask(__name__)

redis = redis.Redis(host='redis', port=6379, db=0)

def hello_world():

return 'Hello, World!' @app .route('/')def hello_world():return 'Hello, World!'

def visitor():

redis.incr('visitor')

visitor_num = redis.get('visitor').decode("utf-8")

return "Visitor: %s" % (visitor_num) @app .route('/visitor')def visitor():redis.incr('visitor')visitor_num = redis.get('visitor').decode("utf-8")return "Visitor: %s" % (visitor_num)

def reset_visitor():

redis.set('visitor', 0)

visitor_num = redis.get('visitor').decode("utf-8")

return "Visitor is reset to %s" % (visitor_num) @app .route('/visitor/reset')def reset_visitor():redis.set('visitor', 0)visitor_num = redis.get('visitor').decode("utf-8")return "Visitor is reset to %s" % (visitor_num)

Put that file under directory demo1. We will store several files under this directory such as:

main.py

requirements.txt

Dockerfile

docker-compose.yml

The snippet above is just a Python code that might not be running properly. We don’t have any dependencies and we can’t figure out which version that might be needed by the web apps.

That’s why we have requirements.txt:

Flask==1.1.2

redis==3.4.1

gunicorn>=19,<20

We could define Python dependencies as many as we need into our web application. This file will be parsed once we are building our Docker image. As I mentioned before, we have to store this file under demo1 directory.

B. Dockerize our web application

Dockerfile is a main recipe of our docker-based web application. It contain multiple line of commands that stated by the developer to build the image for their purposes. As long your project have Dockerfile, you could build it as a standalone container or multiple container using Docker Compose.

Here is a listing of our Dockerfile:

FROM python:3.7-alpine RUN mkdir /app WORKDIR /app ADD requirements.txt /app ADD main.py /app RUN pip3 install -r requirements.txt CMD ["gunicorn", "-w 4", "-b", "0.0.0.0:8000", "main:app"]

Let’s follow the code line by line.

FROM python:3.7-alpine

First, you want to build the image base on the other Docker image with name python:3.7-alpine. This image is the lightweight one than python:3.7 which may have 1GB size of image in your local machine or server.

RUN mkdir /app

Then you create a directory under the root dir within the container. You will have /app directory once your container run.

WORKDIR /app

Using WORKDIR will let you change your current directory inside container when the image is still being built. The default directory is root directory then you move to /app to proceed your building further.

ADD requirements.txt /app ADD main.py /app

In simply, you are trying to copy requirements.txt and main.py to /app directory within your container.

RUN pip3 install -r requirements.txt

With that command, you are able to run Python dependencies which is required by our web application. Everything inside that file will be installed within your container and imported by our web application once the container running.

CMD ["gunicorn", "-w 4", "-b", "0.0.0.0:8000", "main:app"]

So, what is the difference between RUN and CMD? RUN executed your command only once at build time. But CMD let your command run continuously once your image run as a container [2]. If you want to let our web application run forever, we have to use CMD instead RUN.

That’s it. We will build the image once we run our stacks using Docker Compose in the next section. We won’t build the image as a standalone container.

C. Composing our containers using Docker-compose

We will use Docker Compose to orchestrate our containers. With Docker Compose you could use as many container as you want and connect them each other depend on your requirement.

Docker Compose could be configured through YAML file that usually consist of version and services key. You have to choose which version of Docker Compose that you need. Under services key, you will have a lot of containers that you manage.

version: '3' services:

app:

build: .

volumes:

- .:/app

ports:

- "8000:8000"

links:

- redis:redis

depends_on:

- redis

redis:

image: "redis:alpine"

expose:

- "6379"

Let’s call our web application container as app. Then we will build the image from project root directory. Then we mount the volumes from project root directory to /app directory within the container. Afterwards, we map the port 8000 from the container to port 8000 within the host.

We need Redis as datastore that hold value from client served by our web application. That’s why we put redis container as redis as a host within our web application container. So instead we pass localhost to open Redis connection

redis = redis.Redis(host='localhost', port=6379, db=0)

We pass redis to our Python redis library and specify the port that the web application required

redis = redis.Redis(host='redis', port=6379, db=0)

After we have finished with creating docker-compose.yml within our project directory. Now let’s execute the command to build our containers:

$ docker-compose up

Docker Compose logs during build time

Docker Compose logs after build time. It show you logs from each containers

And you can check our containers using this command:

$ docker ps -a | grep demo1

Our containers is running within our machine

Finally, you have built our containers. Now you have Flask web application and Redis running on your machine as container. We will explore the web application in the next section.

Here are other useful Docker Compose commands:

- follow container log and load each 10 line from the container

$ docker-compose logs -f --tail 10 <container_name> - delete network and container that orchestrate by Docker Compose

$ docker-compose down - start existing containers without creating it from the beginning

$ docker-compose start - stop existing containers without destroy the containers

$ docker-compose stop - scale the amount of your container

$ docker-compose scale <app_name>=<num_of_container> - to see another feature just use help command

$ docker-compose help

D. Let’s explore it!

Open your browser to try it’s endpoints:

localhost:8000/

Root endpoint of our web application show “Hello World” message

You will get a “Hello World” response on your browser.

localhost:8000/visitor

After client hit /visitor endpoint, they will get the visitor number keep increasing

After 53 time your web browser refreshed

The more you refresh your browser, visitor count will be stay increasing.

localhost:8000/visitor/reset

If you consider to reset the number back to zero, you could hit this endpoint.

Finally we almost reach the end of this article :D.

Conclusion

Deploy your web application using Docker might be tricky. But it will help you a lot when you have a multiple machines for your web application high availability. On the other hand, you might be help a lot when you need to deploy your new version or rolled it back to the prior version.

Using Docker Compose might be a suitable option to start your web application development locally and manage it easily as containers. You might add another container into your existing stacks and manage it under single file only. So you don’t have to mess up with a lot of configurations within your machine.

You may clone the repository that hold this example here:

https://github.com/ridwanbejo/docker-compose-intro

References

[1] https://blog.aquasec.com/a-brief-history-of-containers-from-1970s-chroot-to-docker-2016

[2] https://nickjanetakis.com/blog/docker-tip-7-the-difference-between-run-and-cmd