DEV Community

Cover image for Deploy Flask The Easy Way With Gunicorn and Nginx!
brandon_wallace
brandon_wallace

Posted on • Edited on

Deploy Flask The Easy Way With Gunicorn and Nginx!

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 and pipenv. We will use pipenv to set up the virtual environment.



$ sudo apt update
$ sudo apt upgrade

$ sudo apt install python3-pip pipenv


Enter fullscreen mode Exit fullscreen mode

Check that pipenv installed correctly by checking the version.



$ pipenv --version
pipenv, version 2022.12.19


Enter fullscreen mode Exit fullscreen mode

Create project directory to hold your project. /var/www/ is a good location.



$ sudo mkdir -p /var/www/myproject


Enter fullscreen mode Exit fullscreen mode

The default permissions for the directory are set to root.



$ ls -ld /var/www/myproject
drwxr-xr-x 2 root root 4096 Feb 20 12:37 /var/www/myproject/


Enter fullscreen mode Exit fullscreen mode

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


Enter fullscreen mode Exit fullscreen mode

Check the to see that the permissions changed to username brandon and group name www-data.



$ ls -ld /var/www/myproject
drwxr-xr-x 2 brandon www-data 4096 Jan 27 12:37 /var/www/myproject/


Enter fullscreen mode Exit fullscreen mode

CD into the /var/www/myproject directory.



$ cd /var/www/myproject


Enter fullscreen mode Exit fullscreen mode

Create a .env file to hold the environmental variables.



$ touch .env


Enter fullscreen mode Exit fullscreen mode

Edit the .env file to add the FLASK_APP and FLASK_ENV environmental variables. With any editor add these two lines:

$ vim .env



FLASK_APP=wsgi.py
FLASK_ENV=production


Enter fullscreen mode Exit fullscreen mode

Start the virtual environment. Pipenv will load the variables in the .env file automatically.



$ pipenv shell

Loading .env environment variables...
Loading .env environment variables...
Creating a virtualenv for this project...
Pipfile: /var/www/myproject/Pipfile
Using /usr/bin/python3 (3.11.2) to create virtualenv...
⠇ Creating virtual environment...created virtual environment [...]
✔ Successfully created virtual environment!
Virtualenv location: /home/brandon/.local/share/virtualenvs/myproject-jyD3CuVy
Creating a Pipfile for this project...
Launching subshell in virtual environment...


Enter fullscreen mode Exit fullscreen mode

Use pipenv to install the dependencies.



$ pipenv install flask gunicorn

Loading .env environment variables...
Installing flask...
Installing gunicorn...
Pipfile.lock not found, creating...
[...]


Enter fullscreen mode Exit fullscreen mode

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


Enter fullscreen mode Exit fullscreen mode

Edit application.py file. 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>'


Enter fullscreen mode Exit fullscreen mode

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)


Enter fullscreen mode Exit fullscreen mode

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'

 * Tip: There are .env or .flaskenv files present. Do "pip install python-dotenv" to use them.
 * Serving Flask app 'wsgi.py'
 * Debug mode: off
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
 * Running on all addresses (0.0.0.0)
 * Running on http://127.0.0.1:5000
 * Running on http://192.168.12.34:5000
Press CTRL+C to quit


Enter fullscreen mode Exit fullscreen mode

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 your 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.

flask-app1.png

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.



(myproject) $ gunicorn --workers 4 --bind 0.0.0.0:5000 wsgi:app


Enter fullscreen mode Exit fullscreen mode

--workers N

Set the --workers to two times 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 networking interfaces on port 5000.

wsgi:app

wsgi is the file name without the .py 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

[2024-02-20 20:57:21 -0500] [4936] [INFO] Starting gunicorn 21.2.0
[2024-02-20 20:57:21 -0500] [4936] [INFO] Listening at: http://0.0.0.0:5000 (4936)
[2024-02-20 20:57:21 -0500] [4936] [INFO] Using worker: sync
[2024-02-20 20:57:21 -0500] [4937] [INFO] Booting worker with pid: 4937
[...]


Enter fullscreen mode Exit fullscreen mode

Press CTRL+C to stop the Gunicorn server.

While you are in the virtual environment check the path of gunicorn. Take note of this path. You will need to know the path to gunicorn to configure the systemd service file. My path is /var/www/myproject/.venv/bin/gunicorn your path might be /home/$USER/.local/share/virtualenvs/myproject-544gQc4M/.venv/bin/gunicorn. The path depends if you have PIPENV_VENV_IN_PROJECT=true set in your .bashrc file or not. If the variable is set to true pipenv will use the .venv in your project directory.



$ which gunicorn

/var/www/myproject/.venv/bin/gunicorn


Enter fullscreen mode Exit fullscreen mode

Now that you have a basic Flask application running with Gunicorn we can set up Nginx.

Install Nginx

Run these commands to install and start the Nginx webserver.



$ sudo apt install nginx

$ sudo systemctl enable nginx

$ sudo systemctl start nginx


Enter fullscreen mode Exit fullscreen mode

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; preset: enabled)
     Active: active (running) since Tue 2024-02-20 21:03:17 EST; 16min ago
       Docs: man:nginx(8)
   Main PID: 5163 (nginx) 
   [...]


Enter fullscreen mode Exit fullscreen mode

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. Pay careful attention to the Environment= and ExecStart= variables. The Environment= variable should lead to the bin directory inside your virtual environment. The ExecStart= path should lead where gunicorn is installed in the virtual environment.



$ sudo vim /etc/systemd/system/myproject.service


Enter fullscreen mode Exit fullscreen mode

[Unit]
Description=myproject.service - A Flask application run with Gunicorn.
After=network.target

[Service]
User=brandon
Group=www-data
Environment="PATH=/var/www/myproject/.venv/bin"
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

User

Sets the user who has permission to the project directory.

Group

Sets the group who has permission to the project directory.

Environment

Sets the path to the bin directory inside the virtual environment.

WorkingDirectory

Sets the base directory where the code for the project is.

ExecStart

Sets the path to the gunicorn executable inside the virtual environment along with the gunicorn command line options.

Now I enable and start the myproject systemd service.



$ sudo systemctl enable myproject

$ sudo systemctl start myproject


Enter fullscreen mode Exit fullscreen mode

Next we need to create the server block/virtual host in Nginx. Create a file in /etc/nginx/sites-available/ with any editor. Add the following content. Change the name myproject.conf to the name of your project.



$ sudo vim /etc/nginx/sites-available/myproject.conf


Enter fullscreen mode Exit fullscreen mode


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;
        }
}


Enter fullscreen mode Exit fullscreen mode

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/


Enter fullscreen mode Exit fullscreen mode

Make sure the link has been created in sites-enabled directory.



$ ls -l /etc/nginx/sites-enabled/ | grep myproject

lrwxrwxrwx 1 root root 41 Feb 20 17:37 myproject.conf -> /etc/nginx/sites-available/myproject.conf


Enter fullscreen mode Exit fullscreen mode

Check the nginx configuration for errors then restart the service.



$ nginx -t

$ sudo systemctl restart nginx

$ sudo systemctl status nginx


Enter fullscreen mode Exit fullscreen mode

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 www.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

or

http://www.myproject

You should seen something similar below.

flask-app2.png

Troubleshooting

Here are some troubleshooting steps...

For when things do not go right there are a few things you can do to troubleshoot.

It is very important to 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


Enter fullscreen mode Exit fullscreen mode

Add logging to Gunicorn by changing the ExecStart line. Add --error-logfile error.log and --log-level debug to the end of the line.

Change the ExecStart line:



ExecStart=/var/www/myproject/.venv/bin/gunicorn --workers 3 --bind unix:/path/to/myproject/myproject.sock wsgi:app


Enter fullscreen mode Exit fullscreen mode

To this:



ExecStart=/var/www/myproject/.venv/bin/gunicorn --workers 3 --bind unix:/path/to/myproject/myproject.sock wsgi:app --error-logfile error.log --log-level debug


Enter fullscreen mode Exit fullscreen mode

Check the Nginx 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                  [::]:* 


Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode




Conclusion

You have learned to configure Gunicorn to run a Flask application. You learned how to run Gunicorn as a systemd service. You learned to configured a virtual host in Nginx. You should now be able to deploy a Flask application to a server.

Follow me on Github.

Feel free to leave questions, suggestions, and comments.

Top comments (15)

Collapse
 
xiaogang profile image
xiaogang

Thanks. Following these steps, I successfully deployed the Flask project using Gunicorp and learned to write services into Linux services

Collapse
 
brandonwallace profile image
brandon_wallace

I am glad that you found my article useful.

Collapse
 
bjarmstr profile image
bjarmstr

So close to getting this working but myproject produces a SSL_error. I had to make a few changes along the way. For instance, pipenv didn't install well without using sudo.
I thought the problem was where pipenv installed my virtual environment. It is not in /var/www/myproject/.venv but way down in /home/pi/.local somewhere but changing folder to this didn't solve the problem.

sudo systemctl status myproject.service produces the following error:
myproject.service:9: Missing '='.

Any ideas on where to go looking to fix this?

Collapse
 
brandonwallace profile image
brandon_wallace • Edited

It looks like you have a syntax error in the myproject.service file. It appears that you are missing an equal sign somewhere. Check this file /etc/systemd/system/myproject.service for syntax errors.

Collapse
 
bjarmstr profile image
bjarmstr

Yes! Application deployed. Thanks for the quick answer, it gave me reassurance that my problem had to be something simple. To start with I didn't realize that the unix statement was a continuation of the ExecStart line, not a new line. I fixed this early-on but didn't restart the service after I did the daemon-reload so my mistake remained!
After a successful launch, I rebooted the computer and Gunicorn didn't start. I added Environment="PATH=/var/www/myproject/.venv/bin" to myproject.service and that seems to have solved problem.
Thanks for writing this step-by-step article.

Thread Thread
 
brandonwallace profile image
brandon_wallace • Edited

Unfortunately, this line in the myproject.service file was wrapped due to the formatting. I added a backslash to the line to resolve the issue. Now it will read as one long line.

ExecStart=/var/www/myproject/.venv/bin/gunicorn --workers 3 --bind unix:/var/www/myproject/myproject.sock wsgi:app
Enter fullscreen mode Exit fullscreen mode

Thanks for letting people know about the Environment="PATH=...." issue. I did not need that variable.

Collapse
 
byterbit profile image
Byter-Bit

Without placing an "index.html" file in the folder I get a "2 directory index of "/var/www/html/myproject2/" is forbidden"
I've tried changing the user and group to www-data and, as a last resort, running "sudo chmod -R 777 myproject2" and then restarting nginx.

When I do place an "index.html" file there it runs, and not the python file.
I am attempting to run the program on an external Ubuntu server using the ip address followed by /myproject2, as in xxx.xxx.xx.xx/myproject2

At least this shows the redirection to the folder is working -
I must say this has been rather hard to debug - more moving parts than I'm used to with Apache running PHP or Node files -
Yours seems to be the best written tutorial I've found. I've spent days running several others and getting nowhere :>(

Collapse
 
brandonwallace profile image
brandon_wallace

Byter-Bit,

There is no need to set the permissions to 777. If you would like to access the /myproject2 route you must create a route in Flask. Look at the @app.route(...) line in the code. The route you need would look something like this:

Example 1:

@app.route('/myproject2')
def custom_route():
    '''Myproject2 page route'''

    return '<h1>My Project 2</h1>'
Enter fullscreen mode Exit fullscreen mode

Then in your browser you would access this custom route like this http://<ip_address>/myproject2. If you set the server_name line in Nginx you would use http://<domain_name>/myproject2 to access that page.

Example 2:

@app.route('/')
def index():
    '''Index page route'''

    return '<h1>Application Deployed!</h1>'
Enter fullscreen mode Exit fullscreen mode

The index route is accessible like this http://<ip_address> or if you set up Nginx you would use this http://<domain_name>.

Collapse
 
ipapop profile image
ipapop

Your tutorial allowed me, after many different tutorials and trials, to get this flask-gunicorn -nginx setup to finally work on MY DESKTOP -ubuntu 22.04 LTS- .
Thank you for it !!
The only place I tripped ,although you specifically warned against it, was in the path to gunicorn in the myproject.service. I used your troubleshooting advices.

Collapse
 
jishwin13 profile image
jishwin13

Hey, I followed the exact same steps but I am getting this error after I host it with nginx
12:10:20 [crit] 142389#142389: *1 connect() to unix:/root/status_report_automation/status_report_automation.sock failed (13: Permission denied) while connecting to upstream, client: 103.99.218.187, server: _, request: "GET / HTTP/1.1", upstream: upstream: "unix:/root/status_report_automatio...", host: ":8080"
Nginx is listening to port 8080

Collapse
 
brandonwallace profile image
brandon_wallace • Edited

It looks like you have put your project in the /root directory.

/root/status_report_automation/
Enter fullscreen mode Exit fullscreen mode

It is better to put it in /var/www/ like this:

$ sudo mkdir /var/www/status_report_automation/
Enter fullscreen mode Exit fullscreen mode

Then set the correct permissions on the directory and files.

$ sudo chown -R $USER:www-data /var/www/status_report_automation/
Enter fullscreen mode Exit fullscreen mode

Make sure your systemd service file looks something like this in the service section:

[Service]
User=YOUR_USER_NAME_GOES_HERE
Group=www-data
WorkingDirectory=/var/www/status_report_automation/
ExecStart=/var/www/status_report_automation/.venv/bin/gunicorn --workers 3 --bind unix:/var/www/status_report_automation/status_report_automation.sock wsgi:app
Enter fullscreen mode Exit fullscreen mode
Collapse
 
garyk2015 profile image
garyk2015

Missing certs and SSL setup, your code shows myproject but the screenshot shows myproject...just saying...

Collapse
 
brandonwallace profile image
brandon_wallace

Thanks. I fixed it. SSL set up is for another article.

Collapse
 
abdomgdy profile image
Abdul-Rahman Magdy

Hi is there a way to get this to work without buying a domain ?
so just using the server IP address to access the app.

Collapse
 
brandonwallace profile image
brandon_wallace

There is no need to buy a domain to get this to work. You can access the web application using the IP address of the server. Using a domain name to access the web application is much easier than using the IP address.