Skip to content

Launch your Django app on a VPS in 15 mins!

archived on 2014-12-04


First things first!

Lately, my shared hosting was too slow for my taste and I decided to move to VPS. I chose Digital Ocean since GitHub Student Package gave me 100$ worth of credit. You will need a ".edu" account to get this credit. You could also get 10$ worth of credit if you sign up using this referral link (It will also help out a hungry student!).

There are several tutorials online to help with initial server setup. I particularly like the tutorials from DigitalOcean since even my tiny brain could understand it! To get started, you can follow this initial server setup (for Ubuntu 14.04). Make sure you complete all 3 tutorials in the series!


Django with uWSGI & Nginx

There are numerous ways to deploy Django apps. uWSGI and Nginx is one such combination that is claimed to be reasonably fast. Nginx (pronounced engine-x) is a light weight HTTP and reverse proxy server and WSGI (Web Server Gateway Interface) is a protocol for the interface between web servers and web applications. It is the standard for Django! uWSGI is an application server container that implements WSGI. More comprehensive tutorial here.

Too formal? In simple terms, when someone asks for a webpage from, lets say, mynewsite.com, there must be some program running on the server that will receive and process that request. Nginx is that program! It will patiently wait and listen to all incoming requests and promptly deliver pages/files it is asked to give. If there were only static files, Nginx would have been sufficient. But our HTML pages are generated by Django using data stored in some database! So in this case, Nginx will in turn have to ask Django for that page. This is where uWSGI comes into play. It will help Nginx and Django talk to each other.

Optional Setup

For the database, it is recommended you move away from SQLite when in production. You could either use MySQL or the more popular PostgreSQL. They both come with a nice GUI. For MySQL, you could setup phpMyAdmin following this guide. For PostgreSQL, you could setup phpPgAdmin following this guide.


1. Installation

Lets install Nginx if we haven't already done so.

sudo apt-get install nginx

Now lets install uWSGI. We will use pip since it has updated versions when compared to debian. Also we will do a system wide install so deactivate your virtual environment if you're using it.

sudo pip install uwsgi

2. Setup uWSGI

At a bare minimum, uWSGI requires a python callable and an endpoint to take requests. Additional configuration can be supplied as command line arguments. A better way is to run uWSGI with a .ini file that has all our configurations. uWSGI comes with a neat little feature called the emperor mode. In this mode, you can tell the uWSGI Emperor process to watch a directory and it will automatically spawn new processes for every .ini file it sees. It will also restart a process if its .ini file changes. Pretty cool right?

If you installed uWSGI using debian (apt-get) then the uWSGI directory would be created at /etc/uwsgi/. If it is not there, lets create the following directories

sudo mkdir /etc/uwsgi
sudo mkdir /etc/uwsgi/apps-available
sudo mkdir /etc/uwsgi/apps-enabled

We will configure the emperor to watch the apps-enabled directory. Create a file called emperor.ini in the /etc/uwsgi/ directory with the following contents.

sudo mkdir /etc/uwsgi
sudo mkdir /etc/uwsgi/apps-available
sudo mkdir /etc/uwsgi/apps-enabled

Now lets install uWSGI. We will use pip since it has updated versions when compared to debian. Also we will do a system wide install so deactivate your virtual environment if you're using it.

# /etc/uwsgi/emperor.ini
[uwsgi]
# This is the directory the emperor will watch for new .ini files
emperor = /etc/uwsgi/apps-enabled

# Set the uid and gid. This could be any user you wish
uid = www-data
gid = www-data

# Set the log file path.
# Make sure the log file has the required write
# permissions for the emperor process.
logto = /var/log/uwsgi/emperor.log

Now we will deploy the emperor process.

uwsgi --ini emperor.ini

If you want the process to run in the background,

uwsgi --ini emperor.ini &

You could also add the command to rc.local to make sure the script runs on startup.

Common issue: The .log file either hasn't been created or doesn't have the right permissions. Make sure it does!


3. Deploy the Django App

To deploy a Django project, lets first create its .ini file and add it to the /etc/uwsgi/apps-available/ directory. The following shows the configuration for my Django project called Grub Club, a project to host all gourmet grub recipes!

sudo apt-get install nginx

Now lets install uWSGI. We will use pip since it has updated versions when compared to debian. Also we will do a system wide install so deactivate your virtual environment if you're using it.

# /etc/uwsgi/apps-available/grub_club.ini
[uwsgi]
# This should point to your Django project directory
chdir   = /home/ostrich/django/grub_club

# This should point to your Django wsgi file.
# This usually lives alongside your settings.py file
module  = grub_club.wsgi:application

# This should point to your python environment
# If you have a virtual environment you like to use,
# specify that here
home    = /home/ostrich/.virtualenvs/django_env

master  = true

# This is the number of cores on your server
processes = 1

# You can either connect using HTTP OR
# you can use unix sockets (recommended)
socket  = /tmp/grub_club.sock
chmod-socket = 664

# Removes all generated sockets and pids when closed
vacuum  = true

# Log file for this Project.
# Make sure the log file has the required write
# permissions for the emperor process.
logto = /var/log/uwsgi/app/grub_club.log

Now lets add a symbolic link to the directory that the emperor is watching.

sudo ln -s /etc/uwsgi/apps-available/grub_club.ini /etc/uwsgi/apps-enabled/

If all goes well, the emperor process must automatically create a process for our Django project. Have a look at /var/log/uwsgi/emperor.log file for any failures.

Common issue: The .log file either hasn't been created or doesn't have the right permissions. Make sure it does!


4. Make Nginx talk to uWSGI

So far we configured uWSGI to run our Django project and handle any request made to the defined endpoint (In my case, its the unix socket /tmp/grub_club.sock). Now, lets configure Nginx to forward any request it gets to this socket. If you are not using a CDN to deliver static and media files, we must also configure Nginx to serve these files. You must run collectstatic first. Lets start by creating a server block and place it in the /etc/nginx/sites-available directory. The following shows the configuration for my Grub Club project.

# /etc/nginx/sites-available/grub_club
upstream grub_club_cluster{
    # This will contain one or more endpoints that uWSGI is listening to
    server unix:///tmp/grub_club.sock;
}
server {
    server_name     grubclub.com;
    charset         utf-8;

    # Configuration to deliver static files
    location ^~ /static/ {
        # This will point to the folder where all the static files reside
        # Same location as Django's STATIC_ROOT
        alias     /var/www/grub_club/static/;
        # Sets the HTTP headers for expiration time
        location ~*  \.(jpg|jpeg|png|gif|ico|css|js|woff)$ {
               expires 60d;
        }
    }
    # Configuration to deliver media files
    location ^~ /media/ {
        # This will point to the folder where all the media files reside
        # Same location as Django's MEDIA_ROOT
        alias    /var/www/portfolio/media/;
        # Sets the HTTP headers for expiration time
        location ~* \.(jpg|jpeg|png|gif){
            expires 365d;
        }
    }
    # All other requests are sent to the Django project
    location / {
        include     /etc/nginx/uwsgi_params;
        uwsgi_pass    grub_club_cluster;
    }
}

Create a symbolic link to sites-enabled and reload the configuration.

sudo ln -s /etc/nginx/sites-available/grub_club /etc/nginx/sites-enabled/
sudo service nginx reload

Tips

By default, all requests to grubclub.com are handled by Nginx. What about www.grubclub.com? Lets add the following server block to the grub_club file to redirect requests to the main domain.

server {
    server_name www.grubclub.com;
    return 301 $scheme://grubclub.com$request_uri;
}

Another useful thing to do is to prevent bots from making a brute force attack on your Django admin page. You could look at Django applications like Django Admin Honeypot or limit the number of requests by configuring Nginx as shown here.