Deploying Flask (Python) Apps to Ubuntu with Gunicorn and Nginx

This article describes all the steps necessary to deploy a Flask (Python framework for creating web applications) application on Ubuntu with Gunicorn and Nginx.

Deploying Flask (Python) Apps to Ubuntu with Gunicorn and Nginx

I was recently playing around with creating web apps using python, where I came across Flask - A micro framework for python to build web applications. Flask is renowned for its straightforward design, which gives developers the freedom to select the elements they desire and customize their apps to meet their needs.

While developing web apps using Flask is pretty much straightforward, being new to Python and Flask I struggled a little while trying to deploy the apps for production on Ubuntu.

Though there are many articles already written and available on the internet to help the deployment process, none of them are having a concrete information describing each step necessary.

In this article I will try to describe all the steps necessary to deploy a Flask application on Ubuntu with Gunicorn and Nginx.

This article assumes that you already have created your web application using Flask locally and have setup following things setup before you start the deployment process.

  1. A server with Ubuntu 22 or higher installed and a non-root user with sudo privileges.
  2. You have Nginx installed on the server.
  3. You have a domain name configured to point to your server.
  4. You have familiarity with the WSGI specification, which the Gunicorn server will use to communicate with your Flask application.

Once you have everything ready, you can start the deployment process.

Installing Python and other necessary components

First step is to install Python and other required libraries and components from the Ubuntu repositories on your Ubuntu server.

We will need Python Package Manager pip to manage Python packages / libraries. You also need to install the Python development files necessary to build some of the Gunicorn components.

Run the following command on the terminal to update the local package index and install the packages.

sudo apt update
sudo apt install python3-pip python3-dev build-essential libssl-dev libffi-dev python3-setuptools

Setting up the Virtual Environment

In this step you’ll set up a virtual environment in order to isolate the Flask application from the other Python files on your system.

Run following command on terminal to install Python Virtual Environment Package.

sudo apt install python3-venv

Once installed move to your application directory and setup the environment.

cd YOUR_PROJECT
python3 -m venv YOUR_PROJECT_VENV

Here YOUR_PROJECT is the directory name for your Flask application (Assuming you have already created a Flask web application) and YOUR_PROJECT_ENV is name given to the Virtual Environment for your application. You can replace these with your choice of names.

Now that Virtual Environment is created your need to activate / run it. Run following command on terminal to do so.

source YOUR_PROJECT_VENV/bin/activate

Once you do this your command prompt will show you that you are in virtual environment now by displaying (YOUR_PROJECT_VENV) in the terminal.

Installing Flask and Gunicorn

Now that we have setup the virtual environment, we need to install the Framework and server to run our Flask application.

Run the following command in your virtual environment to install Flask and Gunicorn

(YOUR_PROJECT_VENV) $ pip install gunicorn flask

Once we have installed the required libraries let's run our application on localhost to make sure everything is working as expected.

But before doing that we need to open the port 5000 (default port for Flask Application) on the firewall, so our Flask application can be served from that port. Run following command to do so.

sudo ufw allow 5000

Ok we are ready to serve our app locally. Let's do it.

  (YOUR_PROJECT_VENV) $ python YOUR_PROJECT_MAIN_FILE.py

If successful terminal will show you that your application is running on SOME_IP on port 5000

Running on http://0.0.0.0:5000/

You can navigate to this address (replace the IP address with your server IP address) in your browser and see your application running.

Ok. first milestone achieved and we are able to run the Flask application locally on Ubuntu server. But this is not the end as we need to setup the production environment for our app. Let's dive into it in next step.

Setup WSGI

You might be wondering what is WSGI and why do we need it to run a Flask web application. Well! WSGI is the Web Server Gateway Interface. It is a specification that describes how a web server communicates with web applications. Why do we need it ? because A traditional web server (Nginx in this case) does not understand or have any way to run Python applications. I am not going into details for WSGI, you can learn more about it by searching on Google 😄 Let's just setup WSGI.

Let's create an entry point for our application, which will tell the Gunicorn server where to start and how to interact with our application.

nano ~/YOUR_PROJECT/wsgi.py

Above command will create a file named wsgi.py on root of our application and open it for editing in nano editor. Just copy paste following into editor and adjust it to your setup.

from YOUR_PROJECT import app

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

Save and close the editor once you are done.

Configuring Gunicorn

Once we have setup the WSGI entry point for our application, its time to setup the Gunicorn web server.

Gunicorn stands for 'Green Unicorn' and it's a Python WSGI HTTP Server for UNIX.

I think this definition of Gunicorn is enough for the scope of this article. You can learn more about it if you want here.

Before we move on we need to check that Gunicorn can serve the application correctly.

We can do this by simply passing it the name of our entry point. This is constructed as the name of the module (minus the .py extension), plus the name of the callable within the application. In our case, this is wsgi:app. Also specify the interface and port to bind to so that the application will be started on a publicly available interface. Run the following command to test it out.

(YOUR_PROJECT_VENV) $ cd ~/YOUR_PROJECT
(YOUR_PROJECT_VENV) $ gunicorn --bind 0.0.0.0:5000 wsgi:app

If you don't see any error on the console and see something like following in your terminal, that means Gunicorn is able to serve your application 😄

Listening at: http://0.0.0.0:5000 

You can check if application is running in browser by replacing the IP address with your server's IP address.

Ok, Now we are in good shape as we know that our application is ready to be served to public. We don't need the virtual environment anymore, so you can deactivate it by running following command.

(YOUR_PROJECT_VENV) $ deactivate

From here on we will not be using virtual environment through the terminal but we will use system’s commands. We will need to do a little setup for that.

We need to create the systemd service unit file. Creating a systemd unit file will allow Ubuntu’s init system to automatically start Gunicorn and serve the Flask application whenever the server boots.

In case you don't know much about Systemd. It is a tool that controls several systems in Linux. Want to learn more about systemd ? Here is a comprehensive resource for that.

Let's create a system unit file with .service extension in /etc/systemd/system directory.

sudo nano /etc/systemd/system/YOUR_PROJECT.service

Above command will create the service file and open it in nano editor, where we have to define the service.

Let's start with Unit section.

[Unit]
Description=Description for gunicorn instance serving YOUR_PROJECT
After=network.target

In the Service section we define following things:

  1. The User under which process will run. This can be regular user account since it is the owner all of the relevant files.
  2. The Group ownership to the www-data group so that Nginx can communicate easily with the Gunicorn processes.
  3. The WorkingDirectory is the root path of our web application.
  4. The Path environmental variable to point to our app's virtual environment.
  5. And the last thing is command to start the service. ExecStart will be doing this.

Overall Service section of the file should look something like this.

[Service]
User=YOUR_USER
Group=www-data
WorkingDirectory=/PATH_TO_YOUR_APP_DIRECTORY
Environment="PATH=/PATH_TO_YOUR_PROJECT_VENV/bin"
ExecStart=/PATH_TO_YOUR_PROJECT_VENV/bin/gunicorn --workers 3 --preload --bind unix:/var/run/YOUR_APPLICATION.sock -m 007 wsgi:app

Remember to replace the values with your own.

Here is breakdown of the Start command:

  1. Start 3 worker processes (This can be adjusted to your project needs by changing --workers parameter)
  2. Will load application code before the worker processes are forked. This way you can save some RAM resources as well as speed up server boot times. This we have defined in --preload parameter.
  3. Create and bind to a Unix socket file, YOUR_APPLICATION.sock, within our /var/run directory. (this is important, because I was trying to put the socket file in my application directory and system was not able to access it. You need to keep it in /var/run directory)
  4. And lastly we are specifying the WSGI entry point wsgi:app

Last section to add into service file is Install section.

[Install]
WantedBy=multi-user.target

This defines how / when the service unit will be started at boot. We want this service to start when the regular multi-user system is up and running.

That's mostly it with the service unit file. Let's save the file and close the editor.

At this point we can start Gunicorn service we created and enable it so that it starts at boot. Run following commands to do so.

sudo systemctl start YOUR_PROJECT
sudo systemctl enable YOUR_PROJECT

If you don't see any error on the terminal it means service has successfully started. It will show you in terminal all the instance that were started.

Phew! we are finally done with setting up the Systemd service to start serving our app using Gunicorn at server startup.

Now we will move on the last section of this article which is setting up the Nginx Server to point to our application running on our server.

Setup Nginx

We need to configure the Nginx server to pass all the requests to our application and this will be done using the socket file.

Let's create the Nginx configuration file. Run following command.

sudo nano /etc/nginx/sites-available/YOUR_PROJECT

In editor add following section to instruct Nginx to start listening on port 80 and define the domain name for your web application. In the location section we instruct the Nginx to pass all the requests to our socket file.

server {
    listen 80;
    server_name YOUR_DOMAINE_NAME;

     location / {
        include proxy_params;
        proxy_pass http://unix:/home/YOUR_USER/YOUR_PROJECT/YOUR_PROJECT.sock;
    }
}

Let's save and close. Our application is now available to Nginx to serve it on port 80. But one last thing is remaining and that is to enable the application on Nginx. This is easy. You can just run the following command to create a symlink for the configuration file as configuration is same for sites_enabled.

sudo ln -s /etc/nginx/sites-available/YOUR_PROJECT /etc/nginx/sites-enabled

At this point you can test if your Nginx configuration is correct without any syntax issues by running the following command.

sudo nginx -t

If no error is reported, it means we are good to start the Nginx serving our application. Let's do so by restarting the Nginx so it takes in account our modified configuration. Fingers crossed 🤞

sudo systemctl restart nginx

Hurray! We have concluded our deployment of Flask application on Ubuntu server. One last thing to do is to close port 5000 on firewall and allow full access to the Nginx server

sudo ufw delete allow 5000
sudo ufw allow 'Nginx Full'

You should be able to see your application being served from your domain at this point. All the best.

Liked it ?

Read more