Introduction
After creating a Flask application you probably want people to see it. This tutorial will show you how to deploy your Flask application on a Linux server Nginx with Gunicorn.
Requirements
- A Server (Debian is used in this tutorial)
- Nginx
- Gunicorn
- Pipenv
The first step in the process is to install pip.
$ sudo apt update
$ sudo apt install python3-pip
We will use pipenv to set up the virtual environment.
$ pip3 install pipenv
Check that pipenv installed correctly by checking the version.
$ pipenv --version
pipenv, version 2020.5.28
Create project directory to hold your project. /var/www/
is a good location.
$ sudo mkdir /var/www/myproject
The default permissions for the directory are set to root.
$ ls -ld /var/www/myproject
drwxr-xr-x 2 root root 4096 Jan 27 12:37 /var/www/myproject/
Change the permissions for the user and group to your username and the group www-data.
$ sudo chown -R $USER:www-data /var/www/myproject
Check the to see that the permissions changed.
$ ls -ld /var/www/myproject
drwxr-xr-x 2 brandon www-data 4096 Jan 27 12:37 /var/www/myproject/
CD into the directory.
$ cd /var/www/myproject
Create a .env file to hold the environmental variables.
$ touch .env
Edit the .env file to add the Flask environmental variables. Add these two lines:
$ vim .env
FLASK_APP=wsgi.py
FLASK_ENV=production
Start the virtual environment. Pipenv will load the variables in the .env file automatically.
$ pipenv shell
[...]
✔ Successfully created virtual environment!
Virtualenv location: /var/www/myproject/.venv
Creating a Pipfile for this project…
Launching subshell in virtual environment…
. /var/www/myproject/.venv/bin/activate
(myproject) brandon@server:/var/www/myproject $
Use pipenv to install the dependencies.
$ pipenv install flask gunicorn
To set up a minimal Flask application create two files application.py and wsgi.py. The main Flask application will be stored in application.py.
wsgi.py will be used to get the application running.
$ touch application.py wsgi.py
Edit application.py. Add the code for a minimal Flask application with one route for the index page.
$ vim application.py
from flask import Flask
app = Flask(__name__)
@app.route('/')
def index():
'''Index page route'''
return '<h1>Application Deployed!</h1>'
Edit the wsgi.py file. Add this code wsgi.py to get the application running.
$ vim wsgi.py
from application import app
if __name__ == '__main__':
app.run(debug=False)
Let’s test run the Flask application!
Make the Flask application listen on all server interfaces by specifying the address ‘0.0.0.0’.
$ flask run --host '0.0.0.0'
* Serving Flask app "wsgi.py" (lazy loading)
* Environment: production
* Debug mode: off
* Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)
* Restarting with stat
Now you can access the application from another computer using a browser, navigate to the server's IP address on port 5000. Make sure port 5000 is not blocked by a firewall so that you will be able to access the Flask application.
Since my server's IP address is 192.168.12.34 I access the Flask application from my laptop with a browser using this address:
http://192.168.12.34:5000
You should see this in the browser.
Press CTRL+C
to stop the Flask development server.
It is time to set up Gunicorn!
If you were able to run the Flask development server successfully use this command to test run the application using Gunicorn.
$ gunicorn --workers 4 --bind 0.0.0.0:5000 wsgi:app
--workers N
Set the --workers to two x the number of cores in your server. Adjust the number later if you have any issues. Do not exceed 12.
--bind 0.0.0.0:5000
This will listen on all server interfaces on port 5000.
wsgi:app
wsgi
is the file name without the extension. app
is the instance of the Flask application within the file. You should see the similar output below.
$ gunicorn --workers 4 --bind 0.0.0.0:5000 wsgi:app
[2021-01-27 14:28:00 -0500] [24662] [INFO] Starting gunicorn 20.0.4
[2021-01-27 14:28:00 -0500] [24662] [INFO] Listening at: http://0.0.0.0:5000 (24662)
[2021-01-27 14:28:00 -0500] [24662] [INFO] Using worker: sync
[2021-01-27 14:28:00 -0500] [24695] [INFO] Booting worker with pid: 24695
Press CTRL+C
to stop the Gunicorn server.
Now that you have a basic Flask application running with Gunicorn we can set up Nginx.
Install Nginx.
$ sudo apt install nginx
$ sudo systemctl enable nginx
$ sudo systemctl start nginx
Check to see if Nginx is running by running this command.
$ sudo systemctl status nginx
● 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 Wed 2021-01-27 15:58:49 EST;
[...]
Now we create the systemd file that runs Gunicorn as a service. Add the following code but change all paths and user names so that they correspond to your set up.
$ vim /etc/systemd/system/myproject.service
[Unit]
Description=myproject
.service - A Flask application run with Gunicorn.
After=network.target[Service]
User=brandon
Group=www-data
WorkingDirectory=/var/www/myproject
/
ExecStart=/var/www/myproject
/.venv/bin/gunicorn --workers 3 --bind unix:/var/www/myproject/myproject
.sock wsgi:app[Install]
WantedBy=multi-user.target
Next we need to create the server block/virtual host in Nginx. Add the following content. Change the name ‘myproject’ to the name of your project.
$ vim /etc/nginx/sites-available/myproject.conf
server {
listen 80;
server_name myproject www.myproject;
access_log /var/log/nginx/myproject.access.log;
error_log /var/log/nginx/myproject.error.log;
location / {
include proxy_params;
proxy_pass http://unix:/var/www/myproject/myproject.sock;
}
}
server_name
is where the domain name goes. That is what you will use to access the web application.
access_log
and error_logs
specify the path to the access and error logs.
location
block is where Nginx reverses proxy back to the Flask application.
Enable the website by creating a link to the sites-enabled directory.
$ sudo ln -s /etc/nginx/sites-available/myproject.conf /etc/nginx/sites-enabled/
Make sure the link has been created in sites-enabled directory.
$ ls -l /etc/nginx/sites-enabled/ | grep myproject
lrwxrwxrwx 1 root root 41 Jan 27 17:37 myproject.conf -> /etc/nginx/sites-available/myproject.conf
Check the nginx configuration for errors then restart the service.
$ nginx -t
$ sudo systemctl restart nginx
$ sudo systemctl status nginx
The Flask application is no longer accessible via the IP address since it is now being served by Gunicorn and Nginx. To access the Flask application you would need to use the name you set in the Nginx server block for the directive server_name
in the Nginx configuration. To access the web page can edit the host file on your desktop/laptop to point the domain to the IP address of your server.
Edit the host file to add point the domain name to the server.
Since my server's IP address is 192.168.12.34 I would add this line to the host file.
192.168.12.34 myproject
Host file location for Linux:
/etc/hosts
Host file location for Windows:
C:\Windows\System32\drivers\etc\hosts
Now I can access the Flask application with a browser via the name.
http://myproject
You should seen something similar below.
Here are some troubleshooting steps...
For when things do not go right there are a few things you can do to troubleshoot.
First. Check carefully for any spelling errors in all the files you edited.
Check systemd.
$ sudo systemctl status myproject.service
$ sudo journalctl -xe
Restart the Gunicorn systemd service.
$ sudo systemctl restart myproject.service
$ sudo systemctl daemon-reload
Add logging to Gunicorn by changing the ExecStart
line.
From this:
ExecStart=/var/www/myproject/.venv/bin/gunicorn --workers 3 --bind unix:/path/to/myproject/myproject.sock wsgi:app
To this:
ExecStart=/var/www/myproject/.venv/bin/gunicorn --workers 3 --bind unix:/path/to/myproject/myproject.sock wsgi:app --error-logfile /home/bw/bin/lanmanager/gunicorn.error.log --log-level debug
Check the log files.
$ less /var/www/nginx/error.log
$ less /var/www/myproject/error.log
Check to see if the Nginx server is listening on port 80.
$ ss -lnt | grep 80
LISTEN 0 511 0.0.0.0:80 0.0.0.0:*
LISTEN 0 511 [::]:80 [::]:*
Use curl to check for a 200 OK response.
$ curl -I http://myproject
HTTP/1.0 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 30
Server: Werkzeug/1.0.1 Python/2.7.16
Date: Wed, 03 Feb 2021 15:18:55 GMT
Feel free to leave questions, suggestions, and comments.
Discussion (2)
Missing certs and SSL setup, your code shows myproject but the screenshot shows myproject...just saying...
Thanks. I fixed it. SSL set up is for another article.