How to make a Python Web App in Virtualenv using Flask, Gunicorn, NGINX on Ubuntu 18.04

February 11, 2019

This article will cover creating a scalable Python Flask web application on Ubuntu 18.04 using Gunicorn and NGINX. The web application also leverages Virtualenv to provide an isolated Python environment and Systemd as a service manager. We loosely are defining web 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.

*This is an updated approach to an older blog post of mine How to make a Scalable Python Web App using Flask, Gunicorn, NGINX on Ubuntu 14.04 from 2015.

I have created a GitHub repo scalable-ubuntu-flask-gunicorn-nginx available for you to get started quickly with a setup.sh file that does all the steps below via one bash script.

If you would like to do this step by step please continue reading below:

Assumptions:

  • You have a Ubuntu 18.04 Server or Cloud Instance
  • You are using ubuntu as your user and have sudo privileges
  • Step 0
    Update your Ubuntu packages

    sudo apt update

    Step 1
    Time Matters! Sync’ing time is important all servers drift which can effect applications negatively.

    sudo apt -y install ntpdate
     
    sudo ntpdate pool.ntp.org
     
    sudo apt -y install ntp

    Step 2
    Install Python Dependencies and Virtualenv, you will be installing Python 3 and leveraging Virtualenv to have an isolated environment for your Python dependencies specific to your application.

    sudo apt -y install build-essential openssl
     
    sudo apt -y install libpq-dev libssl-dev libffi-dev zlib1g-dev
     
    sudo apt -y install python3-pip python3-dev
     
    sudo pip3 install virtualenvwrapper

    Step 3
    Install NGINX, it will act as a reverse proxy for scaling purposes.

    sudo apt -y install nginx

    Step 4
    Configure your .bashrc file so that your ubuntu user can use Virtualenv

    vim ~/.bashrc

    Add the following to the bottom of your .bashrc file

    # default location of virtual environment directories
    export WORKON_HOME=$HOME/.virtualenvs
    # default python version to use with virtualenv
    export VIRTUALENVWRAPPER_PYTHON=/usr/bin/python3
    export VIRTUALENVWRAPPER_VIRTUALENV_ARGS=' -p /usr/bin/python3 '
    source `which virtualenvwrapper.sh`

    Step 5
    Source your .bashrc file to activate your changes

    source ~/.bashrc

    Step 6
    Create a directory to keep applications in

    mkdir /home/ubuntu/flask_apps
     
    cd /home/ubuntu/flask_apps

    Step 7
    Create your Virtualenv project environment named app01_env

    virtualenv app01_env

    Step 8
    Activate your Virtualenv and install Python packages for your application specific to the new project environment. The three packages listed are gunicorn (webserver), flask (Python microframework), and setproctitle (to view your processes friendly name when using ps). After use deactivate to remove yourself from this environment.

    source app01_env/bin/activate
     
    pip install gunicorn
     
    pip install flask
     
    pip install setproctitle
     
    deactivate

    Step 9
    Create your Python Flask application that says Hello World

    vim /home/ubuntu/flask_apps/app01_env/app01.py

    app01.py

    from flask import Flask
    hello = Flask(__name__)
     
    @hello.route("/")
    def greeting():
        return "<h1 style='color:red'>Hello World!</h1>"
     
    if __name__ == "__main__":
        hello.run(host='0.0.0.0')

    Step 10
    Create your webserver gateway interface for the Flask microframework

    vim /home/ubuntu/flask_apps/app01_env/wsgi.py

    wsgi.py

    from app01 import hello
     
    if __name__ == "__main__":
        hello.run()

    Step 11
    Create your Gunicorn config file which holds your Gunicorn settings. Gunicorn is essentially your webserver for your application. The below settings are optimized for a dual core server.

    vim /home/ubuntu/flask_apps/app01_env/gunicorn_config.py

    gunicorn.py

    pidfile = 'app01.pid'
    worker_class = 'gthread'
    workers = 5
    worker_connections = 1000
    timeout = 30
    keepalive = 2
    threads = 2
    proc_name = 'app01'
    bind = '0.0.0.0:8080'
    backlog = 2048
    accesslog = 'access.log'
    errorlog = 'error.log'
    user = 'ubuntu'
    group = 'ubuntu'

    Step 12
    Create a Systemd service file. Systemd is a service manager and in this use case it will manage Gunicorn.

    sudo vim /etc/systemd/system/app01.service

    app01.service

    [Unit]
    Description=Gunicorn for Flask app01
    After=network.target
     
    [Service]
    User=ubuntu
    Group=ubuntu
    WorkingDirectory=/home/ubuntu/flask_apps/app01_env
    Environment="PATH=/home/ubuntu/flask_apps/app01_env/bin"
    ExecStart=/home/ubuntu/flask_apps/app01_env/bin/gunicorn --config=gunicorn_config.py wsgi:hello
     
    [Install]
    WantedBy=multi-user.target

    Step 13
    Start your application via Systemd and enable it to auto start after reboot

    sudo systemctl start app01
     
    sudo systemctl enable app01.service

    Step 14
    Create your applications NGINX config that makes NGINX listen on port 80 and proxies requests to Gunicorn on port 8080. NGINX will help with connection handling increasing performance.

    sudo vim /etc/nginx/sites-available/nginx_app01.conf

    nginx_app01.conf

    server {
        listen 80 default_server;
     
        location / {
            include proxy_params;
          	proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Real-IP	$remote_addr;
          	proxy_set_header Host $http_host;
          	proxy_redirect off;
            proxy_buffers 8 24k;
            proxy_buffer_size 4k;
            proxy_pass http://127.0.0.1:8080;
        }
    }

    Step 15
    Delete your default NGINX conf file, enable your new NGINX conf and restart NGINX

    sudo rm /etc/nginx/sites-enabled/default
     
    sudo ln -s /etc/nginx/sites-available/nginx_app01.conf /etc/nginx/sites-enabled
     
    sudo systemctl restart nginx

    Step 16
    If the server or instance you installed this on has port 80 available for you to reach. Enter the server IP or hostname into your browser and you should see “Hello World!” in red, which tells you your Python Flask application is working.

    Performance Settings

  • Gunicorn is configured with 5 workers leveraging threading (2 per worker) each worker can handle 1000 connections. Typical formula is 2 workers per cpu core + 1 worker
  • Gunicorn Timeout value is set to 30 seconds and keepalive is set to 2 seconds
  • NGINX is set to use 8 buffers and 24K size per buffer when reading a response from the proxied server for a single connection
  • NGINX is set to 4k buffer size used for headers from the pool
  • NGINX worker_processes are set to “auto” for detecting number of cores and setting that value
  • Comments are closed.