A scalable Flask application using Gunicorn on Ubuntu 18.04 in Docker

July 9, 2019

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:

  • You have Docker installed
  • These instructions are were done on a Mac
  • 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!

    Comments are closed.