Deploying Django with Gunicorn and Supervisor

Here at Monmar we deploy all Django applications with Gunicorn and Supervisor. I personally prefer Gunicorn to uWSGI because it has better configuration options and more predictable performance.

In this article we will be deploying a typical Django application. We won't be using async workers because we're just serving HTML and there are no heavy-lifting task in background.

I'm assuming that our Django project was created with Cookiecutter Django, an awesome boilerplate developer by Daniel Roy Greenfeld. I strongly recommend using it for every new Django project.

Our requirements/production.txt file should already contain these two packages:

# WSGI Handler
# ------------------------------------------------
gevent==1.1.1  
gunicorn==19.6.0  

So, we only need to SSH to the server, create a virtualenv and populate it with packages:

$ virtualenv --python=python3.5 .venv
$ source .venv/bin/activate
$ pip3 install -r requirements.txt

Next, we're going to add missing configs to our working copy:

$ mkdir -p deploy/production && cd deploy/production
$ touch gunicorn.conf.py nginx.conf run.sh supervisor.conf

I'm providing real configuration files we're using for our company website. So, please update it for your project needs.

gunicorn.conf.py

bind = 'unix:/tmp/gunicorn-monmar.sock'  
workers = 2  
timeout = 30  

nginx.conf

upstream monmar {  
    server unix:/tmp/gunicorn-monmar.sock fail_timeout=0;
    keepalive 60;
}

server {  
    listen 80;
    listen [::]:80;
    server_name monmar.tech www.monmar.tech monmar.co www.monmar.co;

    location / {
        return 301 https://monmar.tech$request_uri;
    }
}

server {  
    listen 443;
    listen [::]:443;
    server_name monmar.tech www.monmar.tech;

    access_log /var/log/nginx/monmar.access.log;
    error_log /var/log/nginx/monmar.error.log;

    ssl on;
    ssl_certificate /etc/ssl/private/monmar-chain.pem;
    ssl_certificate_key /etc/ssl/private/monmar-private.key;

    ssl_dhparam /etc/ssl/private/dhparams.pem;

    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    ssl_prefer_server_ciphers on;
    ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH";
    ssl_ecdh_curve secp384r1;
    ssl_session_cache shared:SSL:10m;
    ssl_session_tickets off;
    ssl_stapling on;
    ssl_stapling_verify on;

    resolver 8.8.8.8 8.8.4.4 valid=300s;
    resolver_timeout 5s;

    add_header Strict-Transport-Security "max-age=63072000; includeSubdomains; preload";
    add_header X-Content-Type-Options nosniff;

    client_max_body_size 200M;

    location /admin {
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header Host $http_host;

        add_header Cache-Control "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0";

        proxy_pass http://monmar;
    }

    location / {
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header Host $http_host;

        proxy_cache STATIC;
        proxy_cache_valid 200 10m;
        proxy_cache_valid 404 1m;

        proxy_ignore_headers X-Accel-Expires Expires Cache-Control;
        proxy_ignore_headers Set-Cookie;
        proxy_hide_header Set-Cookie;

        proxy_pass http://monmar;
    }

    location /media {
        alias   /var/www/monmar/media;
        expires max;
        access_log off;
    }

    location /static {
        alias   /var/www/monmar/staticfiles;
        expires max;
        access_log off;
    }
}

Please note that we're using a nginx cache zone for the best performance. You can learn more about this technique in my previous article.

run.sh

#!/usr/bin/env bash

cd /var/www/monmar

export $(cat .env) && exec .venv/bin/gunicorn config.wsgi -c deploy/gunicorn.conf.py  

Please pay attention that we're not calling .venv/bin/gunicorn directly, but wrapping it with exec. If you don't do that, your Gunicorn program won't be supervisored, and you won't be able to stop and restart it properly.

supervisor.conf

[program:monmar]
command=/var/www/monmar/deploy/run.sh  
directory=/var/www/monmar  
user=www-data  
stopsignal=KILL  
redirect_stderr=True  
stdout_logfile=/var/log/supervisor/monmar.stdout.log  
stderr_logfile=/var/log/supervisor/monmar.stderr.log  

It's time to commit your changes and deploy them on your server. In the next article I will explain how we automate deployment of our Django projects with Fabric. But, now we will just deploy manually.

Let's install Supervisor on our server. I'm assuming that we're using Ubuntu.

$ sudo apt-get update && sudo apt-get install supervisor

Now let's create symlinks and run our Django application. A friendly reminder: I'm referring an existing website as an example, so please update everything according to your Django project.

$ sudo ln -sf /var/www/monmar/deploy/production/supervisor.conf /etc/supervisor/conf.d/monmar.conf
$ sudo ln -sf /var/www/monmar/deploy/production/nginx.conf /etc/nginx/sites-enabled/monmar
$ sudo chown -R www-data:www-data /var/www/monmar
$ sudo supervisorctl reload  # Load a new config
$ sudo supervisorctl start monmar
$ sudo service nginx restart

Your application should be accessible now. You're welcome to ask questions.

Michael Samoylov

Python, JavaScript and Swift Expert with 12+ years of experience.

Vilnius, Lithuania https://monmar.tech

Subscribe to Michael Samoylov

Get the latest posts delivered right to your inbox.

or subscribe via RSS with Feedly!