Django on Ubuntu using NGINX, Gunicorn, and PostgreSQL

Django is among the best frameworks to build websites in modern days. Although getting started using Django is extremely easy, deploying it to a production server is somehow tricky and where most developers face difficulties. When I got started using Django, deploying it to a live server was the most difficult task for me.

After years of experience, now I’ve got enough experience and skills to deploy Django to a live server easily and using the best practices. In this brief guide, you will learn how to deploy your Django project to a live server using the best practices. We will also optimize Gunicorn backend server to handle traffic effectively.

We will be using an Ubuntu 20.x VM with root or sudo SSH access for this purpose. Moreover, we’ll be using an example hello world app that I have published at GitHub.

Getting Started

Sign into your server using a terminal (or PuTTY on Windows) and run the following command:

# To prevent typing sudo again and again, let's become root
sudo su

# Update the packages cache
apt-get update

If you don’t see any errors, and if the package cache gets updated, we are good to move on.

Installing Needed Packages

We need to install NGINX, PostgreSQL, and some other packages. Run the following command in order to install the packages that we need.

apt-get install nginx python3-pip python3-dev libpq-dev postgresql postgresql-contrib

Once the packages are installed, let’s move forward and optimize NGINX configuration. I’ve taken this awesome configuration file from Gist here.

Optimize NGINX

Go to /etc/nginx/ and replace nginx.conf file using the following configuration:

user helloworld;
worker_processes auto;
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;
worker_rlimit_nofile 100000;
error_log /var/log/nginx/error.log crit;

events {
    worker_connections 4000;
    use epoll;
    multi_accept on;
}

http {
    open_file_cache max=200000 inactive=20s;
    open_file_cache_valid 30s;
    open_file_cache_min_uses 2;
    open_file_cache_errors on;
    access_log off;
    sendfile on;
    tcp_nopush on;
    client_max_body_size 100M;
    tcp_nodelay on;
    gzip on;
    gzip_min_length 10240;
    gzip_comp_level 1;
    gzip_vary on;
    gzip_disable msie6;
    gzip_proxied expired no-cache no-store private auth;
    gzip_types
        text/css
        text/javascript
        text/xml
        text/plain
        text/x-component
        application/javascript
        application/x-javascript
        application/json
        application/xml
        application/rss+xml
        application/atom+xml
        font/truetype
        font/opentype
        application/vnd.ms-fontobject
        image/svg+xml;
    reset_timedout_connection on;
    client_body_timeout 30;
    send_timeout 30;
    keepalive_timeout 90;
    keepalive_requests 100;
    include /etc/nginx/mime.types;
    include /etc/nginx/conf.d/*.conf;
    include /etc/nginx/vhosts.d/*;
}

Now to simplify the vhosts management, let’s delete the NGINX default vhosts directories and create a new directory vhosts.d that we have referenced in our new conf file:

rm -r /etc/nginx/sites-enabled
rm -r /etc/nginx/sites-available
mkdir /etc/nginx/vhosts.d

Let’s confirm that our NGINX configuration doesn’t have any errors:

nginx -t

If no errors encountered, let’s restart NGINX web server:

service nginx restart

Create System User

In above NGINX configuration, you will see that we have specified a system user helloworld at the top. This is because Gunicorn and the system service that we will create for our website will run under this user. Let’s create this system user first:

adduser helloworld

Follow the on-screen instructions and you will be able to create the system user within a few seconds.

Create the Database & User

Now we will create the PostgreSQL user and the database. To ensure that we don’t need to tweak any additional settings in PostgreSQL configuration files to connect to the database, we will create user named as the same system user that we will be using for our website. The username for PostgreSQL will be helloworld too.

Let’s first become the PostgreSQL user, and create the database and user:

sudo -u postgres psql
CREATE DATABASE helloworld;
CREATE USER helloworld WITH PASSWORD 'mysecurepassword';
ALTER ROLE helloworld SET client_encoding TO 'utf8';
ALTER ROLE helloworld SET timezone TO 'UTC';
ALTER ROLE helloworld SET default_transaction_isolation TO 'read committed';
GRANT ALL PRIVILEGES ON DATABASE helloworld TO helloworld;
\q

That’s it. The database and the user has been created and we are ready to move to the next step now.

Install Virtualenv

While installing packages, we installed python3-pip too. Now let’s use pip to install virtualenv. We will be using virtualenv to launch an isolated Python environment for our new website:

pip3 install virtualenv

Create NGINX vhost file

Create a conf file named as helloworld.conf in /etc/nginx/vhosts.d:

nano /etc/nginx/vhosts.d/helloworld.conf

And paste in the following configuration. Be sure to replace example domains with your actual domains:

server {
	listen 80;
    server_name mysite.com www.mysite.com;
    location = /favicon.ico { access_log off; log_not_found off; }
    location /static/ {
        root /home/helloworld/helloworld;
    }

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

Save the file. If you don’t want to use nano, you can use any other file editor or you can even upload the file using SFTP or any other protocol.

Create the system service

Create a service file named as helloworld.service in /etc/systemd/system:

nano /etc/systemd/system/helloworld.service

And paste in the following content:

[Service]
User=helloworld
Group=helloworld
EnvironmentFile=/home/helloworld/helloworld/vars.ini
WorkingDirectory=/home/helloworld/helloworld
Environment="PATH=/home/helloworld/venv/bin"
ExecStart=/home/helloworld/venv/bin/gunicorn --threads 5  --workers 3 --bind unix:helloworld.sock -m 007 helloworld.wsgi

[Install]
WantedBy=multi-user.target

In the above configuration, we are launching Gunicorn server using 3 workers and 5 threads. With this configuration, a total of 3 workers will be launched and a total of 15 threads will be spawned. Specifying the threads is recommended otherwise just 1 thread will be spawned per worker and you may not be able to use the full potential of your CPU.

While specifying the threads and workers, you need to keep this in mind:

num of workers = (num of CPU cores * 2) + 1
num of threads = (num of CPU cores * 4) + 1

This is the recommended formula to specify the workers and threads for Gunicorn. If you are running a less-powerful CPU, then you should replace 4 with 2 to calculate the threads. Not sure how many CPU cores does your server have? Typing the below command in the terminal will output the CPU cores count:

cat /proc/cpuinfo | grep processor | wc -l

Create the virtual environment

Now let’s create the virtualenvironment for our website. We will use this virtual environment to install the packages required by our website and our website will use this virtual environment.

# Become the helloworld user
su helloworld
cd /home/helloworld
virtualenv -p python3.8 venv

It will take a few seconds before your virtual environment will be ready. Once it’s created, typing below command should activate the virtual environment for your current terminal session:

source venv/bin/activate

If you don’t encounter any issues. We are all set. Deactivate the virtual environment as we don’t need it for now:

deactivate

Clone the Django app & install packages

Now let’s clone the example Django app that I’ve created. If you have advanced skills, you can skip clonning the example website and you can use your real project, but I recommend clonning the example web app as you will be able to learn the flow easily when using this example website.

cd /home/helloworld/
git clone git@github.com:rehmatworks/helloworld.git

Within a few seconds, the clonning should complete. Now let’s install the necessary packages:

# Switch to home directory if you aren't there
cd /home/helloworld

# Activate the virtual env
source venv/bin/activate

# CD into the website directory
cd helloworld

# Install the requirements
pip install -r requirements.txt

That’s it. If you don’t notice any errors while installing the packages, we are almost there. Let’s proceed to the next step.

Create the variables file

In the system service that we have created above, you will notice we have referenced an environmental variables file. Let’s create that file now as our website is looking for the database credentials from that file:

touch /home/helloworld/helloworld/vars.ini
nano /home/helloworld/helloworld/vars.ini

Paste the following content in the file and save it:

DB_NAME=helloworld
DB_USER=helloworld
DB_PORT=5432
DB_HOST=localhost
DB_PASS=mysecurepassword

Once you set the values, save the file. We are all done and we are now ready to restart our services.

Restart services

We have completed all the steps successfully. Let’s restart the services:

# If you are still acting as helloworld SSH user, let's switch back to root
exit

# Restart NGINX
service nginx restart

# Restart our system service
service helloworld restart

Now visit your domain. You should be seeing your website live. If your website isn’t live yet, be sure to re-read this guide and ensure that you followed each and every step precisely.

updated_at 28-06-2020