Deploying Django with MariaDB, Nginx, and Gunicorn on Ubuntu 14.04

Django can deployed in a variety of production server environments. One of the most popular methods for deploying Django websites and apps in production is using Gunicorn as the worker process and NGINX (Engine X) as the proxy-server to serve the site's static content.

Gunicorn, otherwise known as Green Unicorn is a Python Web Server Gateway Interface (WSGI) HTTP Server for UNIX and Linux distributions. Gunicorn is a pre-fork worker model ported from Ruby's Unicorn project. The Gunicorn server is highly compatible with various web frameworks, simply implemented, extremely light on server resources, and it's FAST. Real Fast. Like Python fast. One of the benefits of running Django in this configuration is the ability to spawn new worker processes to handle the website's increased network traffic without the need for additional servers, load balancers or additional memory.

I use an Ubuntu 14.04 EC2 image on Amazon Web Services to deploy my Django apps. NGINX acts as a reverse proxy-server that listens for incoming request on the standard web port 80 and forwards those requests to Gunicorn, which is listeting on a port specified by the administrator. Gunicorn is pre-configured to listen on port 8000 This server configuration provides security and performance benefits to serve our Django apps.

Getting Started

Getting started with Gunicorn and NGINX is relatively simple. From your terminal...

sudo apt-get update  
sudo apt-get install python-pip python-dev mariadb-server nginx  
Create a Database and Database User

Start by creating a database and database user for our new Django application.

mysql -u root -p  
CREATE USER 'django_admin'@'localhost' IDENTIFIED BY '<password>';  
CREATE DATABASE django_test;  
GRANT ALL ON django_test.* TO 'django_admin'@'localhost';  
FLUSH PRIVILEGES;  

When you are finished setting up the database user, close out of the MariaDB shell by typing 'exit' at the prompt.

Virtualenv

Next, we are going to set up our virtual environment for our django_test app. If you have not yet installed virtualenv, get the package...

sudo pip install virtualenv  
Create a new Django Project

Now that we have a working virtual environment generator, from the terminal...change the path to /var/www (a good place to store your web site projects)

mkdir ~/django_project  
cd django_project  
virtualenv .env --python==python2.7  

I'm specifying the Python version I want to use for my virtual environment. You do not have to do this if you prefer to use the default Python version installed on the system.

Activate Virtual Environment

Now activate your new virtual environment.

ubuntu@localhost:~/django_project$ source .env/bin/activate  

The terminal prompt will change to show you your new virtual environment is activated.

(.env)ubuntu@localhost:~/django_project$

Now install Django, Gunicorn, Mysql-Python and Redis. Again, from the terminal...

(.env)ubuntu@localhost:~/django_project$ pip install django==1.9.2 gunicorn mysql-python django-redis-cache

We are now ready to use the Django admin to start our new project.

(.env)ubuntu@localhost:~/django_project$ django-admin startproject mydjangoproj
Project Settings

This command will scaffold your starting Django project. Edit the settings.py and configure the database using the credentials we setup earlier.

(.env)ubuntu@localhost:~/django_project$ sudo nano mydjangoproj/settings.py
DATABASES = {  
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'django_test',
        'USER': 'django_admin',
        'PASSWORD': '<password>',
        'HOST': 'localhost',
        'PORT': '3306',
    }
}

I also use redis as my default website cache engine, so you can add the necessary redis-cache config changes to settings.py at the same time. I generally use a socket instead of HTTP on the local server.

To do so, add this to the Django settings file.

CACHES = {  
    'default': {
        'BACKEND': 'redis_cache.RedisCache',
        'LOCATION': '/var/run/redis/redis.sock',
    },
}

Redis is a persistent in-memory data structure server and is much faster and more robust in handling caching than the default Django cache. You should also use Redis for your Django session storage.

One final step before we begin to migrate our database is to add the static root to our settings.py

STATIC_ROOT = os.path.join(BASE_DIR, "static/")  
Complete the Project Set Up

We are finally ready to migrate our initial database and set up our Django web admin user

(.env)ubuntu@localhost:~/django_project$ ./manage.py migrate

(.env)ubuntu@localhost:~/django_project$ ./manage.py createsuperuser

(.env)ubuntu@localhost:~/django_project$ ./manage.py collectstatic

After you confirm the collectstatic command, all of the necessary project static files will be placed in the directory your specified in the STATIC_ROOT entry in settings.py.

Let's test our server configuration by launching Django's built-in web server.

(.env)ubuntu@localhost:~/django_project$ ./manage.py runserver 5000

Now start your web browser of choice and navigate to http://127.0.0.1:5000/admin. If all went well, you should see the Django administration login page.

Login and browse the admin interface and familiarize yourself with the admin layout. When you are finished, hit Ctrl-C to destroy the dev web server.

Test Gunicorn

We need to check the status of Gunicorn and it's ability to serve our new Django project.

Test Gunicorn's by entering the following command at the prompt.

(.env)ubuntu@localhost:~/django_project$ gunicorn --bind 127.0.0.1:5000 mydjangoproj.wsgi:application

This will launch the Gunicorn server using the same settings the Django development server was running. You should go back and re-test the application again to ensure the app is working.

Special note. Your Django admin site will not be styled since Gunicorn doesn't know anything about your site's static files. We'll set up NGINX in a minute for static content delivery.

If you are satisfied your application is working as expected, hit Ctrl-C to kill the server process and return to the terminal.

Deactivate your virtual environment.

(.env)ubuntu@localhost:~/django_project$ deactivate
ubuntu@localhost:~/django_project$  
Create a Gunicorn Start Up File

Now that we have fully tested Gunicorn's ability to serve our WSGI application, we need to have a simple and effective way to start and stop the application. To do this, we can create a Gunicorn start up file.

(.env)ubuntu@localhost:~/django_project$ sudo nano /etc/init/gunicorn.conf

The start up file begins by adding a simple description for the file's purpose.

description "Gunicorn application launcher

start on runlevel [2345]  
stop on runlevel [!2345]

respawn  
setuid <user>  
setgid www-data  
chdir /var/www/django_project/  
exec .env/bin/gunicorn --workers 3 --bind unix:/var/www/django_project/mydjangoproj.sock mydjangoproj.wsgi:application  

Save and close the file. Ctrl-O (hit Enter), then Ctrl-X.

Start the Gunicorn service.

sudo service gunicorn start  
Set Up NGINX to Reverse-Proxy Gunicorn

Gunicorn is now working with 3 worker processes and serving our Django application. Next, we need to set up NGINX to pass traffic to our Gunicorn server process.

You do this by creating a new server block in the NGINX configuration file in the sites-available directory.

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

Open a new server block by informing NGINX to listen on the normal web port 80 and respond to requests for the domain name or IP address.

server {  
    listen 80;
    server_name domain_name_or_IP;
}

The next step is to let NGINX know to ignore any problems with the site's favicon.ico and set the default path for our site's static files. We'll also add a location using proxy_params and then pass traffic to the Gunicorn socket to be executed.

server {  
    listen 80;
    server_name domain_name_or_IP;

    location = /favicon.ico {access_log off; log_not_found off;}
    location /static/ {
        root /var/www/django_project/mydjangoproj;
    }

    location / {
        include proxy_params;
        proxy_pass http://unix:/var/www/django_project/mydjangoproj.sock;
    }
}

Save and close the file. Now create a symbolic link between the file and the sites-enabled directory.

sudo ls -s /etc/nginx/sites-available/django_Proect /etc/nginx/sites-enabled  

You can easily test your NGINX config by...

sudo nginx -t  

If no errors are reported by the NGINX service, celebrate your awesomeness and restart the NGINX service.

sudo service nginx restart  

Finally, navigate to your Django applications IP address or pre-configured domain name to use your new Django application.

Wrap Up

NGINX is now acting as a reverse proxy server which is listening on port 80 and forwarding request to port 8000 where Gunicorn workers are standing by to accept, process and execute requests to the Django application. To increase performance, adjust the number of Gunicorn workers in the gunicorn.conf file we create in our previous steps. Always restart the Gunicorn and NGINX services when making configuration changes.

Craig Derington

Espousing the virtues of Secular Humanism, Libertarianism, Free and Open Source Software, Linux, Ubuntu, CentOS, Terminal Multiplexing, Docker, Python, Flask, Django, MySQL, MongoDB and Git.

comments powered by Disqus