This article will cover creating a scalable Flask application using Gunicorn on Ubuntu 18.04 in Docker. We loosely are defining Flask application to mean serving up a web page via a GET request, however this blog can be used as an example to build a website, Restful API, or any number of things leveraging Docker. Via a Dockerfile we will build a Docker image that you can modify for your own Flask application needs, the example in this blog will be a simple “hello world” web page.
Quick Links:
GitHub repo with files referenced in this blog ubuntu-flask-gunicorn-docker
Docker Hub repo for the example app referenced in this blog nethacker/ubuntu-18-04-flask-gunicorn-example
Docker Hub repo for Ubuntu with Flask and Gunicorn with no app nethacker/ubuntu-18-04-flask-gunicorn
Assumptions:
Step 0
Create a directory for your project.
FROM nethacker/ubuntu-18-04-python-3:python-3.7.3 COPY requirements.txt /root/ RUN pip install -r /root/requirements.txt && useradd -m ubuntu ENV HOME=/home/ubuntu USER ubuntu COPY app01.py wsgi.py gunicorn_config.py /home/ubuntu/ WORKDIR /home/ubuntu/ EXPOSE 8080 CMD ["gunicorn", "-c", "gunicorn_config.py", "wsgi:hello"] |
Step 1
Create a file requirements.txt for your Python dependencies, such as Flask or Gunicorn. For more information about PIP requirements.txt read here.
docker build -t example/ubuntu-flask-gunicorn-docker:latest . |
Add the following to the requirements.txt
docker run --shm-size=256m --detach -p 80:8080 example/ubuntu-flask-gunicorn-docker:latest |
Step 2
Create a file app01.py which will be a simple Flask web app. Flask is a microframework for Python based on Werkzeug and Jinja2. For more information about Flask read here.
http://localhost |
Add the following to app01.py
docker ps |
Step 3
Create a file wsgi.py which will be the gateway from the webserver to your app. For more information about WSGI read here.
docker exec -it {id here} /bin/bash |
Add the following to wsgi.py
from app01 import hello if __name__ == "__main__": hello.run() |
Step 4
Create a file gunicorn_config.py which will be the configurations Gunicorn will utilize. Gunicorn is a Python WSGI HTTP Server for UNIX. For more information about Gunicorn read here.
vim gunicorn_config.py |
Add the following to gunicorn_config.py
*Note we set workers to 2 because if you only have one worker, and it’s handling a slow query, the heartbeat query will timeout which could remove it from a load balancer. Also container schedulers expect logs to come out on stdout and stderr so we have it set in the config as such. We also leverage /dev/shm vs default /tmp to avoid timeouts accessing ram vs disk.
pidfile = 'app01.pid' worker_tmp_dir = '/dev/shm' worker_class = 'gthread' workers = 2 worker_connections = 1000 timeout = 30 keepalive = 2 threads = 4 proc_name = 'app01' bind = '0.0.0.0:8080' backlog = 2048 accesslog = '-' errorlog = '-' user = 'ubuntu' group = 'ubuntu' |
Step 5
Create a file Dockerfile which contains the commands to build your Docker image. For more information on Dockerfile read here.
vim Dockerfile |
Add the following to Dockerfile
*Note we pull a base ubuntu 18.04 image with python 3.7.3 from my Docker Hub repo you could point this to the official Ubuntu repo and add a Python image or install line. All the files are copied into the /home/ubuntu container directory and referenced from there.
FROM nethacker/ubuntu-18-04-python-3:python-3.7.3 COPY requirements.txt /root/ RUN pip install -r /root/requirements.txt && useradd -m ubuntu ENV HOME=/home/ubuntu USER ubuntu COPY app01.py wsgi.py gunicorn_config.py /home/ubuntu/ WORKDIR /home/ubuntu/ EXPOSE 8080 CMD ["gunicorn", "-c", "gunicorn_config.py", "wsgi:hello"] |
Step 6
Build your Docker image
*Note you can -t tag this with your own naming convention
docker build -t example/ubuntu-flask-gunicorn-docker:latest . |
Step 7
Start your Docker container from the Docker image you built. Docker container environments are different then VM’s because of this we set –shm-size to a bigger shared memory size. Gunicorn has a config entry to use shared memory (/dev/shm) vs disk (/tmp) for Gunicorn health checks to avoid timeouts accessing ram vs disk. The setting 80:8080 sets the Docker container to map localhost 80 to container port 8080.
docker run --shm-size=256m --detach -p 80:8080 example/ubuntu-flask-gunicorn-docker:latest |
Step 8
Test your Docker container
If you didn’t modify the Dockerfile you should be able to go to localhost port 80 in a browser and get a red “Hello World” message.
http://localhost |
You can also do the following to access the container with a bash shell to poke around.
Find the running Docker container id.
docker ps |
Get and interactive shell on the container.
docker exec -it {id here} /bin/bash |
Hopefully this basic overview helps you build your own containers!