How to Setup Django Application using MySQL, Gunicorn and Nginx on Ubuntu (VPS)

by hash3liZer . 26 April 2018

How to Setup Django Application using MySQL, Gunicorn and Nginx on Ubuntu (VPS)

In this tutorial, I will demonstrate you to deploy a Django powered application on a Virtual Private Server. I will be using Ubuntu 16.4 but some of the earlier versions (>=14.4) will manifest the same satisfactory response. We will be setting up a database server on MySQL and then gunicorn to serve the Django requests. In the end, we will setup NGINX to serve the application requests and unmask them to django project.

Django

Django is an web application framework written in python. It has its own way of developing and deploying applications with much less effort and necessary structure to remember and propagate. It provides protection and mitigation against various commonly spotted vulnerabilities like SQLi and Cross-site Request Forgery.

Gunicorn

Gunicorn is a Web Gateway Interface written in Python and supports a wide range of frameworks. It is light weight and certainly fast. It can necessarily be spawned as a daemon process at the boot time.

Prerequisites

Before we start, I assume that you have logged in to your server host via SSH (preferably) or another remote utility. Be certain to not use the root account for the application deployment because later on, identification of a command injection would result in the compromisation of web server. Consider trying hostinger as your hosting company, if you haven't yet pick out the right one.

STEP 1

Install Required Packages

First, comes the packages which are NGINX as the web server, MySQL for site Storage, virtualenv for providing the virtual environment. Install them as follows:

sudo -s        # Be the super user
apt-get update
apt-get install nginx mysql-server python-pip python-dev libmysqlclient-dev ufw
# OR for Python 3 Users
apt-get install nginx mysql-server python3-pip python3-dev libmysqlclient-dev ufw

Now, the virtualenv and MySQL packages from python repository.

pip install virtualenv MySQL-python
# OR for python-3
pip3 install virtualenv MySQL-python

If virtualenv installation throws back an error regarding unsupported local string something like:

snipped...
locale.Error: unsupported locale setting

Type it in your terminal and try again
export LC_ALL=C

Before you proceed to next step, make sure everything's installed.

STEP 2

MySQL Server

MySQL will be installed on your Linux distribution with the default preferences which could sometimes not suite your requirements. So, make sure to have a secure installation of MySQL server. While going through the installation process, don't allow it to make remote connections out of deployment server unless really needed.

sudo mysql_secure_installation

Now, navigate to MySQL terminal and create a new secure working environment for the web-application to be deployed.

mysql
MySQL terminal

Ignore mysql > in the below commands. Its just there to indicate that its a mysql terminal

Create a new database and a new user which will have the privileges for the application project.

mysql > CREATE DATABASE databasename;
mysql > CREATE USER databaseuser;

Grant database privileges to the database user and assign the user a passphrase.

mysql > GRANT ALL ON databasename.* TO 'databaseuser'@'localhost' IDENTIFIED BY 'userpass';

Now, Change the database encoding to UTF-8, if it has to be a large database and proceed back to command level using exit command or by pressing CTRL+D

mysql > ALTER DATABASE databasename CHARACTER SET 'utf8';
mysql > exit; 

Remember database name, username and the password assigned to the user. We will later use them in STEP 5.

STEP 3

Application Environment

Move to a preferred directory where you want to keep all the files. Let's choose /home/shellvoide/ for now where shellvoide is my dist username. Move to home directory and create a new folder with the name webproj.

cd /home/shellvoide        # shellvoide is the username
mkdir webproj

Move to project (webproj) directory and setup a new virtual environment and activate it.

cd webproj
virtualenv webenv       # Virtual Environment
source webenv/bin/activate            # Activate it

As the environment activate, name of the environment will be shown at the terminal.

virtual environment

Install Django and guniorn. These packages will be installed and will only be the part of this virtual environment. In this virtual creation, webenv/bin actually works like the real bin. However, the thing to be noted here is virtual environment can access all the variables and files located on the filesystem but doing the opposite will not work.

(webenv) >  pip install django gunicorn
# OR for python 3
(webenv) > pip3 install django gunicorn

STEP 4

Django Application

Time to setup the actual application. We will create a Django Project and after some alterations, deploy the developed application along with the project. Start a new project:

(webenv) > django-admin startproject webapp     # Name the project webapp

This will create a new directory named webapp with the following structure:

webapp/
      manage.py
      webapp/
            __init__.py
            urls.py
            wsgi.py
            settings.py

STEP 5

Configure Application

Now, we will solely concentrate on the application environment. It includes where the Django will look for static files, which database server will be used, how the Django will connect to the database remote server and where the media files like images and videos will be placed. In the second step, we had our credentials for our database. Move to the project directory and add the database created in step 2.

(webenv) > cd webapp
(webenv) > nano webapp/settings.py

The contents of the file will be displayed. Look for the DATABASES block and include the project database.

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'databasename',
        'USER': 'databaseuser',
        'PASSWORD': 'userpass',
        'HOST': 'localhost',
        'PORT': '',    # Server Port
        }
}
database settings

Now, find the ALLOWED_HOSTS line and add the IP address of the remote server or the domain name if you have already purchased one with comma-separated values in the list. Here, the IP address of remote server means the Public IP of server.

ALLOWED_HOSTS = ['domain name' or 'IP']
allowed_hosts django

Specify the directories for static and media files. All the static and media files will store in these directories. These directories will be spawned under the folder specified in the project server which we will see in step 8.

As request from the client, associated the static url, received by the server, the server will look for the files under these directories whose full path will be given by NGINX (server) itself.

STATIC_URL='/static/'
MEDIA_ROOT=os.path.join(BASE_DIR, 'static/')
MEDIA_URL='/media/'
MEDIA_ROOT=os.path.join(BASE_DIR, 'media/')

STEP 6

Migrations and Testing

Run the migration commands and create a new super user account for the project admin Interface. Supposing we are in the project directory, run:

(webenv) > python manage.py makemigrations
(webenv) > python manage.py migrate
(webenv) > python manage.py createsuperuser

Allow the port 5000 to be used as the public port using ufw and then run the application server on port 5000. The below second command will collect all the static files used in the application and will place them under a single directory.

(webenv) > ufw allow 5000
(webenv) > python manage.py collectstatic
(webenv) > python manage.py runserver 0.0.0.0:5000

Coming to the browser from the client side and navigating to the server IP on port 5000 will show up the default django development document. You can also try navigating to the admin interface by adding /admin ahead of the IP address. Try logging in to the superuser account and see if everything's work fine.

django web application

Break the server execution and do the same thing using gunicorn service. This time gunicorn load up the application interface, not the django. Gunicorn will show exactly the same web interface but without any styling applied to it because the server software makes it know where the static files are placed and which is not yet setup. Supposing the command to be run from the same directory where the manage.py file is located:

(webenv) > gunicorn --bind 0.0.0.0:5000 webapp.wsgi:application

Here, the highlighted word webapp is the lower sub-directory of the main project directory whose name is also webapp. If you again navigate to the IP from a browser, as mentioned earlier, the document will be displayed but without any static media. After that, we will no longer be working in the virtual environment, so deactivate it.

(webenv) > deactivate

Now, you are free to upload your application files and directories. Upload your application using a file upload utility like sftp

STEP 7

Gunicorn daemon process

We have tested the application with gunicorn. Let's create systemd daemon process for the gunicorn service. So, that we could have it on the startup, running in the background as a job. To do so, create a new service file under systemd directory and name it gunicorn.

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

We will manually write up the service and tells the service how to execute. Supposing the project and environment to be located under /home/shellvoide/webproj, service configuration goes like the following. Write this configuration and save the service file.

[Unit]
Description=gunicorn service
After=network.target

[Service]
User=shellvoide
Group=www-data
WorkingDirectory=/home/shellvoide/webproj/webapp
ExecStart=/home/shellvoide/webproj/webenv/bin/gunicorn --access-logfile - --workers 3 --chdir /home/shellvoide/webproj/webapp/ --bind unix:/home/shellvoide/webproj/webapp/webapp.sock webapp.wsgi:application

[Install]
WantedBy=multi-user.target

Service Breakdown

We opened a Unit tab which has the meta information of the service and its dependency. Afterward, we opened a Service tab which makes the service execute. In this section, User and Group variable are used for service owners and provides it neccassery privileges.

WorkingDirectory variable commands the service to execute in the specified directory whereas ExecStart provides the necessary command to be executed. In the end, Install section links the services in case the service is spawned as a bootup process.

Save the file and start gunicorn service

sudo systemctl enable gunicorn
sudo systemctl start gunicorn

After executing both of these commands. Make sure gunicorn is running. If everything goes fine as intended, you will see an active status in the output. This will also create a socket file in the projects directory.

systemctl status gunicorn
gunicon running

If an error occurs, use journalctl to enlist the details of gunicorn error and try to remove it.

sudo journalctl -u gunicorn

In case you perform alterations in the project, then it would neccassery to reload and restart the gunicorn service that would be done easily by restarting the service

systemctl daemon-reload
systemctl restart gunicorn
systemctl reload gunicorn

STEP 8

NGINX Server

Create a new project file for nginx server under sites-available directory. This would define how the server will respond to a client request.

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

Define a Server block and specify the server name or IP address of your web server with the port to listen which must be 80 for a web application. Down that comes the location for favicon for which we will ignore errors if server isn't able to find the file.

server {
       listen 80;
       server_name IP or DNS address
       location = /favicon.ico {access_log off;log_not_found off;}
}

Then comes the directories for static and media files. Specify the directories where you want to keep the media files. And at the end define a location block to pass the request that will be received by the Django project.

server {
       listen 80;
       server_name IP or DNS address;
       location = /favicon.ico {access_log off;log_not_found off;}
       
       location /static/ {
            root /home/shellvoide/webproj/webapp;    
       }

       location /media/ {
            root /home/shellvoide/webproj/webapp;
       }

       location = / {
            include proxy_params;
            proxy_pass http://unix:/home/shellvoide/webproj/webapp/webapp.sock;
      }
}

At the end of Step 5, we designated the directories for static and media files. Those folders will be spawned under the path /home/shellvoide/webproj/webapp/.

Suppose a request is received something like /media/picture.jpg from a client. Now the full path for the server, so that it locates the required information, is /home/...../webapp/media/picture.jpg. Save the file.

STEP 9

Server Completion

Enable the site project by creating its link in the enabled sites directory, located under nginx config directory.

ln -s /etc/nginx/sites-available/webapp /etc/nginx/sites-enabled/webapp

Test the project configuration, you've just linked to enabled directory.

nginx -t
nginx configuration OK

Change firewall rules for the production server. We've allowed port 5000 for traffic communication due to testing purposes which we will not use for the web application. Allow the firewall to pass traffic through port 80

ufw delete allow 5000
ufw allow 'Nginx Full'

Restart nginx and gunicorn services and test your site again.

systemctl restart gunicorn
systemctl restart nginx

Now, we have our django web application in process on the Internet.

Conclusion

We've projected a Django web-application with MySQL database in a virtual environment. So, that it will not be get affected by other OS components. Then we've setup gunicorn service which works in the background and handle clients request to Django.

In the end, we configured Nginx server which takes the clients requests and forwards them without modifications. Thus, Django provides a cleaner way of sorting applications and keeps their working irrespective of other web material.