uWSGI-Flask-Nginx
by joe

Posted on 2018-10-12



Deployment of a Flask application for public access requires a real webserver be installed in front of the application. That, in turn, requires, a mechanism for linking the webserver to the Flask application.

In this post, we'll install the uWSGI package and use it to link a rudimentary Flask application to an Nginx server. As part of the uWSGI installation, we'll set up a Python virtual environment to house both the Flask application and the uWSGI package.

In order to follow this post, you should have a recent version of Python and Nginx (see sidebar).


uWSGI Installation

NOTE: As of January 28, 2019, these same instructions work for installing uWSGI version 2.0.17.1.

We used my Antsle to create a Ubuntu 16.04 installation (named "helium") where we have installed recent releases of Python and Nginx (see sidebar).

We're going to install uWSGI in a Python virtual environment, so first we set up a new virtual environment and activate it. NOTE: You can, of course, set up your virtual environment anywhere you have rights, but my common practice is to put them all in my home account under a single "venvs" directory. [[Future Link to menv/genv functions.]]


$ cd                       # /home/joe is the working directory
$ mkdir venvs              # directory for virtual environments
$ cd venvs                 # 
$ python3 -m venv sam      # make sam virtual environment
$ source sam/bin/activate  # activate the virtual environment

Once the virtual environment is activated, the system prompt is prefixed with the name of the virtual environment - (sam). Check the Python version. Next we use which -a to check for the multiple python3 installations: Ubuntu distro, our Python 3.7.0 installation, and the python3 in the (sam) virtual environment.


(sam) joe@helium:~/venvs$ python --version
Python 3.7.0

(sam) joe@helium:~/venvs$ which -a python3
/home/joe/venvs/sam/bin/python3
/usr/local/bin/python3
/usr/bin/python3

With the virtual environment in place, we can install Flask and uWSGI. NOTE: The uWSGI install will fail unless you have a complete Python installation. In particular, you need to have the python3-dev package and the libssl-dev package installed. (See this post.)

Both Flask and uWSGI are installed in the virtual environment. Check the versions.


(sam) joe@helium:~/alex$ pip install flask
(sam) joe@helium:~/alex$ pip install uwsgi


(sam) joe@helium:~/venvs$ which flask
/home/joe/venvs/sam/bin/flask

(sam) joe@helium:~/venvs$ flask --version
Flask 1.0.2
Python 3.7.0 (default, Oct 19 2018, 14:09:51)
[GCC 5.4.0 20160609]

(sam) joe@helium:~/venvs$ which uwsgi
/home/joe/venvs/sam/bin/uwsgi

(sam) joe@helium:~/venvs$ uwsgi --version
2.0.17.1

Installations Complete: Here's the Plan


Now everything we need is installed: Python, Nginx, Flask and uWSGI. This is what we are going to do:

1. We set up and run a simple Flask application without using either Nginx or uWSGI. Test the application using curl.

2. We hook up our rudimentary Flask application to the Nginx server by using a uWSGI configuration file.

3. As a bonus, we set up a second virtual environment ("western") with all the trimmings - uWSGI, Flask application, Nginx service, etc. - and run both applications at the same time.


Rudimentary Flask Application

There are many different ways to structure a Flask application. We'll use the following directory structure.


/home/joe
    |
    |-- /alex       (project directory)
        |
        | -- /paa   (application directory)
        .    |
        .    | -- /static  (css, js, etc.)
        .    | -- /templates  (html files)
        .    |
        .    | -- __init__.py
        .    | -- routes.py
        .    | :
        .    | :
        |
        | -- config.py
        | -- run_me.py
        | :
        | :

This structure is a little overblown for the rudimentary Flask application we are going to build, but it is illustrative of the setup for a simple application (meaning no Blueprints). Also, we have made a few files and directories with artificially distinct names so that dependencies are a little clearer than in most tutorials.

File: __init__.py (shown below)


from flask import Flask

ned = Flask(__name__)
ned.config.from_object('config')

NOTE: Only the root name of the config.py file (shown below) is used in the ned.config.from_object() call.

File: routes.py (shown below)


from paa import ned

@ned.route('/')
def slash():
    title = "<title>Ned's Greeting</title>"
    greeting = '<span style="color: blue"><b>Howdy Doodly, Neighbor!'
    return f'{title}\n{greeting}\n'

NOTE: Our default practice is to use single quotes for strings, but the apostrophe in "Ned's Greeting" necessitates double quotes for the title value.

File: run_me.py (shown below)


from paa import ned
from paa import routes

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

NOTE: Flask defaults to serving the application at address localhost:5000 unless some other host and port are specified in the "ned.run()" statement. As you'll see later, there is no need for those parameters when using uWSGI and Nginx in this example.

File: config.py (shown below)


SECRET_KEY = 'ivegotasecret'

We've included a Flask SECRET_KEY configuration value here for the sake of completeness. It is not necessary in the example shown above. In any real Flask application (using Nginx and uWSGI), though, you're going to use the "session" object at some point, and you cannot do that unless you have set the SECRET_KEY configuration value. See this StackOverflow question/answer for a full description of the Flask "session" object.



Test Flask App in Flask Development Environment

In order to test the Flask application using the curl command, we need to have two terminal sessions - one to execute the Flask app and one to execute the curl command.

To run the Flask application, set up in the .../alex directory:


(sam) joe@helium:~/alex$ export FLASK_APP=run_me.py
(sam) joe@helium:~/alex$ flask run
 * Serving Flask app "run_me.py"
 * Environment: production
   WARNING: Do not use the development server in a production environment.
   Use a production WSGI server instead.
 * Debug mode: off
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
.
.
.
[ctl-C to end the application after the curl test]

In another terminal window, enter the curl command:


joe@helium:~$ curl localhost:5000
<title>Ned's Greeting</title>
<span style="color: blue"><b>Howdy Doodly, Neighbor!

Now we leave this minimally operational Flask application on the shelf while we put Nginx and uWSGI together.


Set uWSGI Parameters to Work with Flask Application

The uwsgi.ini file sets values for all of the parameters required to link a Python-Flask application to the Nginx server.

The module parameter identifies the Python file to run (run_me.py) and the Flask application object (ned).

The master parameter is a standard setting for production environments.

The processes parameter is commonly set to 5 as a default. To get an optimal setting requires experimentation with the application under load.

The socket parameter provides the name of the socket connection between uWSGI and Nginx. Note that the socket value is also identified in the Nginx configuration file. These have to match in order for uWSGI to link correctly with Nginx - a common mechanism for coordinating application elements.

The chmod-socket parameter is supposed to provide the "uWSGI user" access to the socket. The value 664 is specified in the documentation, but it did not work for us, so we show it here as 666, which did work for us.

The vacuum parameter directs uWSGI to delete the Unix socket when the uWSGI server terminates.

The uid and gid parameters identify the user and group running the uWSGI server.

The die-on-term parameter directs uWSGI to "brutally reload all the workers and the master process" when the application exits.

The uwsgi.ini parameter file is shown below:


[uwsgi]
module=run_me:ned

master = true
processes = 5

socket = baker.sock
chmod-socket = 666  
vacuum = true

uid = joe
gid = www-data

die-on-term = true

Setup Nginx to Work With uWSGI Parameters

The Nginx configuration file shown below links up with the uWSGI server via the include and uwsgi_pass parameters.

/etc/nginx/conf.d/helium.conf


server {
    listen 8181;
    server_name localhost;

    location / {
        include   uwsgi_params;
        uwsgi_pass  unix:/home/joe/alex/baker.sock;
    }
}

The /etc/nginx/conf.d/helium.conf file is included in the last line of the standard "starter" configuration file that comes with installation of the Nginx server from the Nginx.org site.


user  nginx;
worker_processes  1;

error_log  /var/log/nginx/error.log warn;
pid        /var/run/nginx.pid;


events {
    worker_connections  1024;
}


http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    keepalive_timeout  65;

    #gzip  on;

    include /etc/nginx/conf.d/helium.conf;
}

Run Nginx and uWSGI Servers

To restart the Nginx server:


$ sudo service nginx restart

In order to start uWSGI properly for the .../alex/paa application, you have to activate the sam virtual environment, then run the uWSGI server.

The --ini uwsgi.ini parameter directs uWSGI to our .ini file.

The --daemonize uwsgi.log parameter runs this instance of the uWSGI server as a daemon process and directs the server to write its log output to uwsgi.log.

The --safe-pidfile /tmp/alex.pid parameter saves the pid for this uWSGI server process to the file "/tmp/alex.pid". That pid number is referenced to "gracefully" reload the uWSGI server or to stop it. (See this.)


(sam) joe@helium:~/alex$ uwsgi --ini uwsgi.ini --daemonize uwsgi.log --safe-pidfile /tmp/alex.pid

And here's the greeting displayed from the command line with "curl localhost:8181":


(sam) joe@helium:~$ curl localhost:8181
<title>Ned's Greeting</title>
<span style="color: blue"><b>Howdy Doodly, Neighbor!</b></span>

There we have it: the Flask application is joined to Nginx via uWSGI.

Twice the Fun

But we could not resist checking this out.

One immediate implication of installing uWSGI into a Python virtual environment is that we should be able to run multiple Flask applications from separate virtual environments - each through its own uWSGI connection to Nginx. And it turns out that we can.

We're just going to sketch this implementation because it is so straightforward. We created a second virtual environment named "western" and installed both Flask and uWSGI as before. We made a project directory named "scifi" and a "noir" directory for the Flask application. The listings below show the substitutions in the western/scifi/noir setup. Compare to the sam/alex/paa setup above.

scifi/noir/__init__.py


from flask import Flask

hitchcock = Flask(__name__)
hitchcock.config.from_object('config')

scifi/noir/views.py


from noir import hitchcock

@hitchcock.route('/')
def slash():
    title = f"<title>Confusion Cinema</title>"
    greeting = f'<span style="color: blue"><b>Some like hot, psycho birds!</b></span>\n'
    return f'{title}\n{greeting}\n'

scifi/run_fi.py


from noir import hitchcock
from noir import views

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

scifi/config.py


SECRET_KEY = 'ivegotasecret'

scifi/uwsgi.ini


[uwsgi]
module=run_fi:hitchcock

master = true
processes = 5

socket = marilyn.sock
chmod-socket = 666
vacuum = true

uid = joe
gid = www-data

die-on-term = true

We distinguish the two separate Flask applications by serving them via separate ports. The uWSGI servers takes care of linking the Flask applications to the correct ports for the Nginx server.


server {
    listen 8181;
    server_name localhost;


    location / {
        include   uwsgi_params;
        uwsgi_pass  unix:/home/joe/alex/baker.sock;
    }
}

server {
    listen 8080;
    server_name localhost;

    location / {
        include   uwsgi_params;
        uwsgi_pass  unix:/home/joe/scifi/marilyn.sock;
    }
}

We set up all of this on a Ubuntu machine, not on our local Mac. But the Ubuntu machine is accessible within our LAN. As a result, we was able to access both Flask applications using the browser on our Mac.

The results are shown below:







Python and Nginx Installed
In order to follow this presentation, you'll need to have recent versions of Python and Nginx installed on your system. The example code uses f-strings, so you'll need Python 3.6 or later to use the code as-is.

See previous posts for Python and Nginx installation instructions.

uWSGI is Enormous
uWSGI is a much larger toolset than what is presented in this post.

uWSGI offers server interfaces for various languages and platforms: WSGI, PSGI, Rack, Lua WSAPI, CGI, PHP, Go, etc., as well as facilities for scaling and optimizing server-based applications.

Check it out here.
Relativistic Frame
It's easy to get lost in the multiple frames of reference presented in this post.

We have two separate virtual environments.

We have two separate Flask applications.

We have two separate uWSGI installations.

Our test system (Ubuntu) is separate from my development system (MacOS).

If you don't run uWSGI as a daemon, you need two separate terminal windows for testing.

We used separate ports (i.e., 8080 and 8181) to serve the separate Flask applications.

Even "Toy" Code Is Hard to Present
This post presents just a small bit of application code to demonstrate the use of uWSGI to link a Python-Flask application to an Nginx web server.

But it still is easily our longest post to date. It takes a lot of time and testing - as well as a lot of words - to present and explain even this little bit of code and expect others to have a realistic hope of understanding.

Later in this series, we're going to work with much larger chunks of code. As a realistic matter, that will necessitate pulling an entire application from Bitbucket and doing full installations of Python, Flask, Nginx and uWSGI.
Another Antsle Project
This project is based on new, clean installations of Python 3.7.0 and the latest release of Nginx. The 'helium' system is a Ubuntu 16.04 LXC with fully updated and upgraded packages.

We do not have any relationship with Antsle other than as a customer. They do not pay us for these plugs. We really like their stuff.
Other Takes
Others have put out blog posts or official docs describing the linkage between uWSGI and Nginx - and either Python or Django or Flask.

Flask Official Docs

Chris Warren's Blog

uWSGI Docs Quickstart

DigitalOcean has docs that used to be pretty good, but we did not find a current version of their docs when we wrote this post.

Comments

It will be some time yet before we get a comments section working here. In the meantime feel free to send comments via email. On this site our name is Joe Python. The email address is our first name at joepython.com.

Edited: 2019-01-30 20:55:12(utc) Generated: 2019-06-10 17:29:58(utc)