How to deploy Python 3 Flask application on Raspberry Pi 3 with Raspbian Stretch Lite, Nginx, Supervisor, Virtualenv and Gunicorn

Raspberry Pi 3 Model B boards make good gifts for programmers and if someone had gifted you with one, you may want to use it as a control center for interacting with various IoT sensors and devices at home.

In such a situation, you may want to build a Python 3 Flask application to present the web interface for accessing the control center.

After you had built that Python 3 Flask application, the next step will be to deploy it on your Raspberry Pi 3 for serving HTTP requests.

This post discusses how you can deploy a Python 3 Flask application on Raspberry Pi 3 with Raspbian Stretch Lite, Nginx, Virtualenv and Gunicorn.

Creating a simple Python 3 Flask application as a proof of concept

Let us create a simple Python 3 Flask application to demonstrate the deployment of Python 3 Flask application on our Raspberry Pi 3 with Raspbian Stretch Lite, Nginx, Supervisor, Virtualenv and Gunicorn.

Project structure of our simple Python 3 Flask application

The simple application consists of a directory with the following contents:

simpleapp
|--requirements.txt
|--start.sh
|--src
   |--__init__.py
|--static

Specifying Flask as a dependency in requirements.txt

The requirements.txt file is for specifying the Python 3 dependencies that our Python 3 Flask application is going to need. Since this is a minimalistic application, the requirements.txt contains only the following line:

Flask==0.12.2

Creating start.sh for Supervisor to start the Python 3 Flask application

The shell script is intended for the Supervisor daemon to start our Python 3 Flask application. The shell script contains the following content:

source simpleappenv/bin/activate
gunicorn --workers 5 --bind unix:simpleapp.sock -m 007 src:app
deactivate

The simpleappenv directory contains a virtual environment that we will created later with virtualenv. In this shell script, we first get into the virtual environment by sourcing the activate script that resides in simpleappenv/bin directory.

Once the virtual environment is activated, we will then use the gunicorn binary to run our Python 3 Flask application. The command tells Gunicorn to use 5 workers for handling HTTP requests directed at the Python 3 Flask application. Gunicorn will listen for HTTP requests via the simpleapp.sock unix socket. Only processes running the same user as this start.sh script can communicate with Gunicorn through the simpleapp.sock unix socket. Gunicorn will also look into the src package to find an app variable to realise the Python 3 Flask application.

We will install a version of gunicorn that can run Python 3 applications into the virtual environment later.

The deactivate command will be run when the Python 3 Flask application got terminated by whatever reason. In such a case, the executor of the shell script will get out of the virtual environment.

Building the Python 3 Flask application inside src/__init__.py

The src/__init__.py Python script will be run when the src directory is being read by Python. The src/__init__.py file contains the following Python code:

from flask import Flask
app = Flask(__name__)
 
@app.route('/')
def hello_world():
    return 'Hello there, welcome to our app!'

In this file, we import Flask, construct a Flask object and set it to the app variable. We then decorate a function to return the message Hello there, welcome to our app! for HTTP requests directed at the root URL.

Using the static directory to serve as the root directory for Nginx

The static directory is meant to serve as the root directory for Nginx. We will use this directory to keep images, CSS, JavaScript and other kinds of static files.

Setting up Raspbian Stretch Lite on Raspberry Pi 3 to run Python 3 applications

As a first step to the deployment of your Python 3 Flask application, follow this tutorial to setup Raspbian Stretch Lite on Raspberry Pi 3 to run Python 3 applications. After doing so, you will get your Raspberry Pi 3 running the Raspbian Stretch Lite operating system with the following software pieces:

  • SSH server which enables you to configure Raspbian Stretch Lite remotely.
  • Python 3 for running your Python 3 Flask application.
  • Python 3 package manager (pip3) for installing the dependencies for your Python 3 Flask application.
  • Virtualenv for you to create an isolated environment for running your Python 3 Flask application.

Getting our Python 3 Flask application project files into our Raspberry Pi 3 via SSH

Assuming that our Raspberry Pi 3 is accessible via the IP address 192.168.1.109 and we are at the directory that contains the simpleapp directory, we will run the following shell command to perform the copy over to the home directory of the pi user:

scp -r simpleapp pi@192.168.1.109:~

After the files are transferred over to your Raspberry Pi 3, run the following command to get into your Raspbian Stretch Lite:

ssh pi@192.168.1.109

Once you log into your Raspbian Stretch Lite via SSH, move the project files to the /var/flaskapp directory by running the following command:

sudo mkdir /var/flaskapp
sudo mv ~/simpleapp /var/flaskapp/

Once you had moved the project files, make the /var/flaskapp/simpleapp/start.sh executable by the owner:

sudo chmod 744 /var/flaskapp/simpleapp/start.sh

Installing Nginx on Raspbian Stretch Lite

Nginx runs very well on Raspberry Pi 3 and serves as a solid reverse proxy server for proxying HTTP traffic to different upstream servers. With Nginx in the same box as our Python 3 Flask application, we have the flexibility to complement the Python 3 Flask application with applications written in another language in the future.

To install Nginx, run the following commands:

sudo apt-get update
sudo apt-get install nginx -y

After the installation had completed, verify the installation by running the following command:

sudo systemctl status nginx

This command should give you output similar to the following if Nginx had installed successfully:

● nginx.service - A high performance web server and a reverse proxy server
   Loaded: loaded (/lib/systemd/system/nginx.service; enabled; vendor preset: enabled)
   Active: active (running) since Sat 2018-03-03 13:35:15 +08; 42s ago
     Docs: man:nginx(8)
  Process: 17632 ExecStart=/usr/sbin/nginx -g daemon on; master_process on; (code=exited, status=0/SUCCESS)
  Process: 17629 ExecStartPre=/usr/sbin/nginx -t -q -g daemon on; master_process on; (code=exited, status=0/SUCCESS)
 Main PID: 17633 (nginx)
   CGroup: /system.slice/nginx.service
           ├─17633 nginx: master process /usr/sbin/nginx -g daemon on; master_process on;
           ├─17634 nginx: worker process
           ├─17635 nginx: worker process
           ├─17636 nginx: worker process
           └─17637 nginx: worker process

Mar 03 13:35:15 raspberrypi systemd[1]: Starting A high performance web server and a reverse proxy server...
Mar 03 13:35:15 raspberrypi systemd[1]: Started A high performance web server and a reverse proxy server.

Installing Supervisor on Raspbian Stretch Lite

Next up is the installation of Supervisor. Supervisor is a convenient tool for running applications as a server daemon. We will use Supervisor to run Gunicorn in an isolated environment.

To install Supervisor, run the following command:

sudo apt-get install supervisor -y

Once the command completes, run the following command to verify your installation:

sudo systemctl status supervisor

You should see output similar to the following if Supervisor had been installed successfully:

● supervisor.service - Supervisor process control system for UNIX
   Loaded: loaded (/lib/systemd/system/supervisor.service; enabled; vendor preset: enabled)
   Active: active (running) since Sat 2018-03-03 13:52:12 +08; 3s ago
     Docs: http://supervisord.org
 Main PID: 18293 (supervisord)
   CGroup: /system.slice/supervisor.service
           └─18293 /usr/bin/python /usr/bin/supervisord -n -c /etc/supervisor/supervisord.conf

Mar 03 13:52:12 raspberrypi systemd[1]: Started Supervisor process control system for UNIX.
Mar 03 13:52:14 raspberrypi supervisord[18293]: 2018-03-03 13:52:14,741 CRIT Supervisor running as root (no user in config file)
Mar 03 13:52:14 raspberrypi supervisord[18293]: 2018-03-03 13:52:14,743 WARN No file matches via include "/etc/supervisor/conf.d/*.conf"
Mar 03 13:52:14 raspberrypi supervisord[18293]: 2018-03-03 13:52:14,794 INFO RPC interface 'supervisor' initialized
Mar 03 13:52:14 raspberrypi supervisord[18293]: 2018-03-03 13:52:14,796 CRIT Server 'unix_http_server' running without any HTTP authentication checking
Mar 03 13:52:14 raspberrypi supervisord[18293]: 2018-03-03 13:52:14,797 INFO supervisord started with pid 18293

Preparing the virtual environment for running our Python 3 Flask application on Raspbian Stretch Lite

The next step is to prepare the virtual environment for running our Python 3 Flask application. To do so run the following commands:

cd /var/flaskapp/simpleapp
virtualenv -p python3 simpleappenv
source simpleappenv/bin/activate
pip install -r requirements.txt
deactivate

After the commands complete, we will have the virtual environment for running our Python 3 Flask application. This virtual environment will be available at the /var/flaskapp/simpleapp/simpleappenv directory.

Installing Gunicorn inside the virtual environment

The next step is to install Gunicorn into the virtual environment that we had created. To do so, we run the following commands:

cd /var/flaskapp/simpleapp
source simpleappenv/bin/activate
pip install gunicorn
deactivate

Installing gunicorn inside the virtual environment will ensure that Supervisor get the correct version of Gunicorn to run our Python 3 Flask application. Although it is possible to indicate Gunicorn as a dependency in requirements.txt, we left it out so that our Python 3 Flask application remains agnostic to the WSGI server that we use for deployment.

Change owner of the Python 3 Flask application directory to www-data

After we had installed Gunicorn inside the virtual environment, the next step will be to set the www-data user as the owner of our Python 3 Flask application:

sudo chown -R www-data:www-data /var/flaskapp/simpleapp

Creating the Supervisor configuration file to run our Python 3 Flask application via Gunicorn

By default, the Supervisor daemon will look inside the /etc/supervisor/conf.d directory to look for instructions to get applications running. To get the Supervisor daemon to run our Python 3 Flask MVP as its subprocess, we first point nano to a configuration file inside the /etc/supervisor/conf.d directory:

sudo nano /etc/supervisor/conf.d/simpleapp.conf

And then paste in the following contents:

[program:simple-flask-app]
directory=/var/flaskapp/simpleapp
command=/bin/bash -E -c ./start.sh
autostart=true
autorestart=true
stopsignal=INT
stopasgroup=true
killasgroup=true
user=www-data

This set of configurations will ensure that our Python 3 Flask application releases binded port(s) during a supervisor restart. In addition, we also indicated to Supervisor that we wanted to run our Python 3 Flask application as the www-data user.

After we had created this set of configurations, we can restart Supervisor to get it to run our Python 3 Flask application for us:

sudo systemctl restart supervisor.service

You can verify whether your Python 3 Flask application is running successfully by using supervisorctl:

sudo supervisorctl

In the supervisor control panel, you will see something similar to the following output if your Python 3 Flask application is running successfully:

simple-flask-app                 RUNNING   pid 24444, uptime 0:00:24
supervisor> 

You can also see the output from Gunicorn by running the following command in supervisorctl:

tail simple-flask-app stdout

Which will give output similar to the following:

[2018-03-03 14:42:04 +0800] [24447] [INFO] Starting gunicorn 19.7.1
[2018-03-03 14:42:04 +0800] [24447] [INFO] Listening at: unix:simpleapp.sock (24447)
[2018-03-03 14:42:04 +0800] [24447] [INFO] Using worker: sync
[2018-03-03 14:42:04 +0800] [24450] [INFO] Booting worker with pid: 24450
[2018-03-03 14:42:04 +0800] [24451] [INFO] Booting worker with pid: 24451
[2018-03-03 14:42:04 +0800] [24452] [INFO] Booting worker with pid: 24452
[2018-03-03 14:42:04 +0800] [24453] [INFO] Booting worker with pid: 24453
[2018-03-03 14:42:04 +0800] [24454] [INFO] Booting worker with pid: 24454

We will also see the /var/flaskapp/simpleapp/simpleapp.sock unix socket, which is where Nginx will proxy HTTP requests to.

Setting up Nginx to proxy HTTP request to our Python 3 Flask application

Once we are sure that Supervisor is running our Python 3 Flask application with Gunicorn, the next step is to configure Nginx to proxy HTTP requests to it. Suppose we want to reach our Python 3 Flask application via the domain name simpleapp.techcoil.com, we first use nano to create the /etc/nginx/sites-enabled/simpleapp.techcoil.com file:

sudo nano /etc/nginx/sites-enabled/simpleapp.techcoil.com

And then include the following contents:

server {

    server_name simpleapp.techcoil.com;
    listen 80;
    root /var/flaskapp/simpleapp/static;

    location / {
        try_files $uri @simpleapp-flask;
    }

    location @simpleapp-flask {
        include proxy_params;
        proxy_pass http://unix:/var/flaskapp/simpleapp/simpleapp.sock;
    }

}

With this set of Nginx configurations, we told Nginx to listen on port 80 for HTTP requests directed at the domain simpleapp.techcoil.com. For every such request, we tell Nginx to reference the directory /var/flaskapp/simpleapp/static as the document root.

The location / block tells Nginx to map request to a file inside the /var/flaskapp/simpleapp/static directory and return it as a HTTP response if there is one. If the file does not exist, Nginx should issue an internal redirect to the location @simpleapp-flask block.

The location @simpleapp-flask block proxies HTTP requests to the /var/flaskapp/simpleapp/simpleapp.sock unix socket with some common HTTP headers defined inside the /etc/nginx/proxy_params file.

Accessing our Python 3 Flask application with a computer in the same network

To verify that our Python 3 Flask application is accessible via simpleapp.techcoil.com, we will edit the host file on a computer that is in the same network as our Python 3 Flask application.

Add the following entries inside the host file to map the simpleapp.techcoil.com domain to the IP address of the Raspberry Pi 3 that is running our Python 3 Flask application:

192.168.1.109 simpleapp.techcoil.com

After you had done so, you should be able to use your browser to access your Python 3 Flask application via http://simpleapp.techcoil.com. The Python 3 Flask application should return you the HTTP response containing the Hello there, welcome to our app! message.

About Clivant

Clivant a.k.a Chai Heng enjoys composing software and building systems to serve people. He owns techcoil.com and hopes that whatever he had written and built so far had benefited people. All views expressed belongs to him and are not representative of the company that he works/worked for.