Django + Gunicorn + Supervisor + Nginx

Posted on Jan 20, 2018 #python #django

As a beginner, I found it quite hard to deploy a Django project in the production environment. After several tries, finally, I got it all together. In this post, I am going to show you how to deploy Django 2.0 project inside Python 3.6 virtual environment using Gunicorn, Supervisor, and Nginx in Ubuntu 16.06.

Install Necessary Packages

Open a terminal and run the following commands:

sudo add-apt-repository ppa:jonathonf/python-3.6  
sudo apt-get update
sudo apt-get install python3.6 python-pip python-dev libpq-dev postgresql postgresql-contrib supervisor nginx

This will download and install Python 3.6, pip, PostgreSQL, Gunicorn, Supervisor, Nginx web server, and other necessary packages.

Create PostgreSQL DB and User

Change to the PostgreSQL system user:

sudo su postgres
psql

Create a database for the project:

CREATE DATABASE myblogdb;

Create a database user for the project:

CREATE USER mybloguser WITH PASSWORD 'password';

Give the new user access to administer the new database:

GRANT ALL PRIVILEGES ON DATABASE myblog TO mybloguser;

Exit out of the PostgreSQL user’s shell session by typing:

\q
exit

Create Virtual Environment

Update pip and install virtual environment:

sudo pip install --upgrade pip
sudo apt-get install virtualenv
sudo pip install --upgrade virtualenv

Create a project directory. I will be creating the directory in /var/www. Don’t forget to add appropriate permissions to the directory.

mkdir /var/www/myproject
cd /var/www/myproject

Create Python virutal environment and activate it:

virtualenv --python=python3.6 venv
source venv/bin/activate

Now install Django, Gunicorn and psycopg2 using pip:

pip install django==2 gunicorn psycopg2

Create Django Project

Create a Django project:

django-admin.py startproject myblog

Change the Django database settings with our PostgreSQL database information.

vim myblog/myblog/settings.py
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql_psycopg2',
        'NAME': 'myblogdb',
        'USER': 'mybloguser',
        'PASSWORD': 'password',
        'HOST': 'localhost',
        'PORT': '',
    }
}

Change the static root and media root:

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

Add our project domains in ALLOWED_HOSTS:

ALLOWED_HOSTS = ['127.0.0.1', 'localhost', 'www.localhost']

Set DEBUG to False.

DEBUG=False

Migrate the initial database schema to our PostgreSQL database:

cd myblog
./manage.py makemigrations
./manage.py migrate

Create a superuser for the project:

./manage.py createsuperuser

Collect all of the static content into the directory location we configured:

./manage.py collectstatic

Finally, run the Django development server:

./manage.py runserver 0.0.0.0:8000

Open browser and negivate to http://127.0.0.1:8000/. You will see the default index page of the application.

Create Gunicorn File

Gunicorn will enable communication between Nginx and Django project. First, we’ll use just Gunicorn to display our project on 127.0.0.1:8000. Now navigate inside /var/www/myproject/myblog/ and run this command:

gunicorn myblog.wsgi:application

It will start Gunicorn server and we’ll be able to see the Django index page on 127.0.0.1:8000. Press Ctrl+C to stop Gunicorn and to create the actual Gunicorn script in /var/www/myproject/gunicorn-start.sh:

#!/bin/bash

NAME="myblog"                                   # Name of the application
DJANGODIR=/var/www/myproject/myblog             # Django project directory
SOCKFILE=/var/www/myproject/run/gunicorn.sock   # we will communicate using this unix socket
USER=nginx                                      # The user to run as
GROUP=webdata                                   # The group to run as
NUM_WORKERS=4                                   # How many worker processes should Gunicorn spawn
DJANGO_SETTINGS_MODULE=myblog.settings          # Which settings file should Django use
DJANGO_WSGI_MODULE=myblog.wsgi                  # WSGI module name

echo "Starting $NAME as `whoami`"

# Activate the virtual environment
cd $DJANGODIR
source /var/www/myproject/venv/bin/activate
export DJANGO_SETTINGS_MODULE=$DJANGO_SETTINGS_MODULE
export PYTHONPATH=$DJANGODIR:$PYTHONPATH

# Create the run directory if it doesn't exist
RUNDIR=$(dirname $SOCKFILE)
test -d $RUNDIR || mkdir -p $RUNDIR

# Start your Django Unicorn
exec gunicorn ${DJANGO_WSGI_MODULE}:application \
  --name $NAME \
  --workers $NUM_WORKERS \
  --user $USER \
  --bind=unix:$SOCKFILE

Make the script executable:

chmod +x gunicorn-start.sh

Create Supervisor Configuration Files

Check Supervisor status:

sudo service supervisor status

If Supervisor is not running, then start Supervisor:

sudo service supervisor start

Create a Supervisor configuration file in /etc/supervisor/conf.d/ directory and name it myblog.conf:

[program:myblog]
command = /var/www/myproject/gunicorn-start.sh                    ; Start app
user = nginx                                                      ; User to run as
stdout_logfile = /var/www/myproject/logs/gunicorn-supervisor.log  ; Where to write log messages
redirect_stderr = true     

Reread the configuration files and update Supervisor to start the project:

sudo supervisorctl reread
sudo supervisorctl update

It can also be started manually using:

sudo supervisorctl start myblog

Create Nginx Server Configuration

Create Nginx server configuration in /etc/nginx/sites-enabled/ directory and name it myblog:

upstream myblog_app_server {
  server unix:/var/www/myproject/run/gunicorn.sock fail_timeout=0;
}

server {
    listen 8000;
    server_name localhost;
    return 301 $scheme://www.localhost$request_uri;
}

server {
    listen 8000;
    server_name www.localhost;

    client_max_body_size 4G;

    access_log /var/www/myproject/logs/nginx-access.log;
    error_log /var/www/myproject/logs/nginx-error.log;

    location = /favicon.ico { access_log off; log_not_found off; } 

    location /static/ {
        autoindex on;
        alias /var/www/myproject/static/;
    }

    location /media/ {
        autoindex on;
        alias /var/www/myproject/media/;
    }

    location / {
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        proxy_redirect off;
        if (!-f $request_filename) {
            proxy_pass http://myblog_app_server;
            break;
        }
    }

    # For favicon
    location  /favicon.ico {
        alias /var/www/myproject/static/img/favicon.ico;
    }
    # For robots.txt
    location  /robots.txt {
        alias /var/www/myproject/static/robots.txt ;
    }
    # Error pages
    error_page 500 502 503 504 /500.html;
    location = /500.html {
        root /var/www/myproject/static/;
    }
}

Enable the virtual servers and restart Nginx:

sudo ln -s /etc/nginx/sites-available/myblog /etc/nginx/sites-enabled/myblog
sudo service nginx restart

That’s all. Now, Open http://127.0.0.1:8000 and if everything goes well then you will see the default index page of the application again. Our Django application is production ready.

I like to keep all the related files organized. So, finally the complete structure will look like this:

var
├── etc
|   ├── supervisor
|   |   └── conf.d
|   |       └── myblog
|   └── nginx
|       ├── sites-available
|       |   └── myblog
|       └── sites-enabled
|           └── myblog
└── www
    └── myproject
        ├── logs
        |   ├── gunicorn-supervisor.log
        |   ├── nginx-access.log
        |   └── nginx-error.log
        ├── myblog
        |   ├── manage.py
        |   └── myblog
        |       ├── __init__.py
        |       ├── settings.py
        |       ├── urls.py
        |       └── wsgi.py
        ├── run
        |   └── gunicorn.sock
        ├── static
        ├── media
        ├── gunicorn-start.sh
        └── venv

To create a basic Django application, visit Writing your first Django app.
For further learning, visit Django documentation.

comments powered by Disqus