Abstract

In this article we will to go through the process of deploying a flask app on a Linux server. We will use gunicorn as a WSGI server to communicate with our flask app, and Nginx as a proxy server between the gunicorn server and the client.

We need gunicorn between flask and nginx because the flask development server although good for debugging is weak and will not stand in production, so we need gunicorn as a wsgi server to communicate with flask.

Contents

Install pre-requisite packages

Firstly, we need to make sure that python3 is installed on the Linux, run the following command to test if python exists:

$ python3.9 --version
Python 3.9.9

However, if python is not installed, you can install it using the following commands:

Update the repository

$ sudo apt update

Install python

$ sudo apt install python3.9

Now we need to make sure that pip (python's package manager) is installed. Run the following command:

$ python3.9 -m pip --version
pip 20.0.2 from /usr/lib/python3/dist-packages/pip (python 3.9)

If pip is not installed then you can install the same using this command:

$ sudo apt install python3.9-pip

Create Python Virtual Environment

Before installing any of the packages that we are going to use, we need to create a python virtual environment to keep different versions of packages separated.

We will create a separate directory to store our project files:

$ mkdir flask-gunicorn

Move to the directory that contains your project and create a virtual environment named venv (or any name):

$ cd flask-gunicorn
$ python3.9 -m venv venv

The above command will create a directory named venv/ that contains everything related to the virtual environment including the binary file that activates the virtual environment, to activate venv virtual environment run the following command:

$ source venv/bin/activate

Once we install python packages, they will be stored in venv/lib/python-version/site-packages

Install flask and gunicorn packages

Once the virtual environment is activated, we are ready to install the packages that we need:

(venv) $ python -m pip install flask gunicorn

Setup Flask Web Application

Now since Flask is successfully installed, we will create python web application using flask. We will start by creating a python file and naming it "app.py":

from flask import Flask

app = Flask(__name__)

@app.route('/')
def index():
    return 'Hello world!'

if __name__ == '__main__':
    app.run()

To start the python web application, just execute app.py using python binary as shown below:

$ python app.py
 * 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: off

Access 127.0.0.1:5000 on your browser of the same Linux server and you should be able to get "Hello World!" message.

Hit Ctrl+C on your terminal to exit the web application once you are finished.

Configure Gunicorn

Now that our basic web app is up and running, we will continue to configure Gunicorn. Using our Flask application from earlier, we can get it up and running using just a few steps

Create WSGi Entry Point

First, we need to create a python file that will be used as an entry point to our application by gunicorn, we will name it wsgi.py:

from app import app

if __name__ == "__main__":
    app.run()

Access Flask web app using Gunicorn

Gunicorn offers a lot of command line options (flags) which can be used to tune the performance of the server to match your needs, the most commonly used options are -w to specify the number of workers that the server will use and --bind which specify the interface and port to which the server should bind (0.0.0.0 will be your server's public IP address)

Now we can test the gunicorn server and see whether it can run the flask app, use the following command to start the gunicorn server with 4 workers -w (You can increase or decrease the number of workers depending on your server's specs), we also need to specify the interface and the port to which the server should bind using the --bind command line option:

(venv) $ python -m gunicorn -w 4 --bind 127.0.0.1:8000 wsgi:app
[2021-11-03 14:27:01 +0300] [15406] [INFO] Starting gunicorn 20.1.0
[2021-11-03 14:27:01 +0300] [15406] [INFO] Listening at: http://127.0.0.1:8000 (15406)
[2021-11-03 14:27:01 +0300] [15406] [INFO] Using worker: sync
[2021-11-03 14:27:01 +0300] [15407] [INFO] Booting worker with pid: 15407
[2021-11-03 14:27:01 +0300] [15408] [INFO] Booting worker with pid: 15408
[2021-11-03 14:27:02 +0300] [15409] [INFO] Booting worker with pid: 15409
[2021-11-03 14:27:02 +0300] [15410] [INFO] Booting worker with pid: 15410

As seen in the above output, the gunicorn server is listening on port 8000 of the localhost, and 4 workers have started each with a different process Id (PID). If you visit 127.0.0.1:8000/, you will see the root directory of your website (same as we say with python web app on port 5000 earlier:

Configuring Nginx

Let's start by installing nginx:

$ sudo apt install nginx

Then navigate to the nginx directory:

$ cd /etc/nginx/

This directory contains all the files related to nginx, we need to create a configuration file that will make nginx act as a proxy for our flask app.

The main configuration file is the one named nginx.conf, by convention, this file is not touched by developers or sys-admins, new configuration files are created in the sites-available/ directory and then sym-linked to the /sites-enabled/directory.

Let's create a new file called my-server in the sites-available/ directory:

server {
    listen 80;

    location / {
        include proxy_params;
        proxy_pass http://unix:/tmp/my-server/ipc.sock;
    }
}

Next run the sudo nginx -t to make sure that the syntax of the configuration file is ok

$ sudo nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

Once the syntax check passes, create a symbolic link of this file into sites-enabled directory:

$ sudo ln -s /etc/nginx/sites-available/my-server /etc/nginx/sites-enabled/

$ sudo ls -l  /etc/nginx/sites-enabled/
total 0
lrwxrwxrwx 1 root root 34 Nov 03 10:56 default -> /etc/nginx/sites-available/default
lrwxrwxrwx 1 root root 36 Nov 03 11:05 my-server -> /etc/nginx/sites-available/my-server

If everything is fine run sudo nginx -s reload for the configuration file to take place:

$ sudo nginx -s reload

The above configurations instructs nginx to listen on port 80 and proxy all the connections to the socket that we created earlier, so that gunicorn can read from the socket and allows our flask app to respond, then gunicorn takes the response from the flask app and writes it to the socket so that nginx can read from the socket and return the response to the user.

Summary

In this article, we went through the process of deploying a flask app using gunicorn and nginx. This setup allows us to utilize the concurrency of gunicorn and nginx and also facilitates the scaling process in case of an expansion.


Comments

comments powered by Disqus