PageView

How to Deploy a Django Application to Digital Ocean

How to Deploy a Django Application to Digital Ocean (Picture: https://www.pexels.com/photo/aerospace-engineering-exploration-launch-34521/)

DigitalOcean is a Virtual Private Server (VPS) provider. In my opinion it’s a great service to get started. It’s cheap and very simple to setup. In this tutorial I will guide you through the steps I go to deploy a Django application using Ubuntu 16.04, Git, PostgreSQL, NGINX, Supervisor and Gunicorn.

In this tutorial we will be deploying the following Django application: github.com/sibtc/urban-train

It’s just an empty Django project I created to illustrate the deployment process. If you are wondering about the name, it is the repository name suggestion GitHub gave.

If you don’t have a DigitalOcean account yet, you can sign up using the link below and get $10 credit to get started:

Get $10 free credit on DigitalOcean. (Disclaimer: this is referral link, meaning you and me get credit.)

Anyway! Let’s get started.


Create a New Droplet

Pick the Ubuntu 16.04.1 distribution:

Digital Ocean

Select the size of the Droplet (cloud server):

Digital Ocean

Select the region you want to deploy:

Digital Ocean

Finally pick a name for the Droplet:

Digital Ocean

And click on Create. After that you will see the recently created droplet in your profile:

Digital Ocean

You will receive the root password via email. Now pick the IP Address and ssh into the server:

ssh root@107.170.28.172

SSH

You will be asked to change the root password upon the first login.

If you are using a Unix-like operating system you can use the terminal. If you are on Windows, you can perhaps download PuTTY.

Also if you prefer, you can use the Digital Ocean’s console:

Digital Ocean Console


Installing the Server Dependencies

First thing let’s upgrade the packages:

sudo apt-get update
sudo apt-get -y upgrade
PostgreSQL

Install the dependencies to use PostgreSQL with Python/Django:

sudo apt-get -y install build-essential libpq-dev python-dev

Install the PostgreSQL Server:

sudo apt-get -y install postgresql postgresql-contrib
NGINX

Install NGINX, which will be used to serve static assets (css, js, images) and also to run the Django application behind a proxy server:

sudo apt-get -y install nginx
Supervisor

Supervisor will start the application server and manage it in case of server crash or restart:

sudo apt-get -y install supervisor

Enable and start the Supervisor:

sudo systemctl enable supervisor
sudo systemctl start supervisor
Python Virtualenv

The Django application will be deployed inside a Python Virtualenv, for a better requirements management:

sudo apt-get -y install python-virtualenv

Configure PostgreSQL Database

Switch users:

su - postgres

Create a database user and the application database:

createuser u_urban
createdb urban_prod --owner u_urban
psql -c "ALTER USER u_urban WITH PASSWORD '123'"

PS: Make sure to pick a secure password! I’m using 123 for simplicity sake.

We can now go back to the root user, simply exit:

exit

Configure The Application User

Create a new user with the command below:

adduser urban

Usually I just use the application name. You will be asked a few questions. Sample of the output below:

Adding user `urban' ...
Adding new group `urban' (1000) ...
Adding new user `urban' (1000) with group `urban' ...
Creating home directory `/home/urban' ...
Copying files from `/etc/skel' ...
Enter new UNIX password:
Retype new UNIX password:
passwd: password updated successfully
Changing the user information for urban
Enter the new value, or press ENTER for the default
  Full Name []:
  Room Number []:
  Work Phone []:
  Home Phone []:
  Other []:
Is the information correct? [Y/n]

Add the user to the list of sudoers:

gpasswd -a urban sudo

Switch to the recently created user:

su - urban

Configure the Python Virtualenv

At this point we are logged in with the urban user (or whatever named you selected). We will install our Django application in this user’s home directory /home/urban:

virtualenv .

Activate it:

source bin/activate

Clone the repository:

git clone https://github.com/sibtc/urban-train.git

This is how the /home/urban directory should look like at the moment:

urban/
 |-- bin/
 |-- urban-train/  <-- Django App (Git Repository)
 |-- include/
 |-- lib/
 |-- local/
 |-- pip-selfcheck.json
 +-- share/

First open the urban-train directory:

cd urban-train

Install the project’s dependencies:

pip install -r requirements.txt

At this point you will need to set the database credentials in the settings.py file:

settings.py

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql_psycopg2',
        'NAME': 'urban_prod',
        'USER': 'u_urban',
        'PASSWORD': '123',
        'HOST': 'localhost',
        'PORT': '',
    }
}

PS: There are better and secure ways to handle SECRET_KEY, database credentials etc. I’m editing it directly in the settings.py file for the sake of simplicity. The focus of this tutorial is on the deployment process itself.

Migrate the database:

python manage.py migrate

Collect the static files:

python manage.py collectstatic

Test if everything is okay:

python manage.py runserver 0.0.0.0:8000

Access the IP Address of your server using port 8000. In my case, 107.170.28.172:8000.

Test Deployment

This is just a test. We won’t be using the runserver to run our application. We will be using a proper application server to securely serve our application.

Hit CONTROL-C to quit the development server and let’s keep moving forward.


Configure Gunicorn

First install Gunicorn inside the virtualenv:

pip install gunicorn

Create a file named gunicorn_start inside the bin/ folder:

vim bin/gunicorn_start

And add the following information and save it:

/home/urban/bin/gunicorn_start

#!/bin/bash

NAME="urban_train"
DIR=/home/urban/urban-train
USER=urban
GROUP=urban
WORKERS=3
BIND=unix:/home/urban/run/gunicorn.sock
DJANGO_SETTINGS_MODULE=urban_train.settings
DJANGO_WSGI_MODULE=urban_train.wsgi
LOG_LEVEL=error

cd $DIR
source ../bin/activate

export DJANGO_SETTINGS_MODULE=$DJANGO_SETTINGS_MODULE
export PYTHONPATH=$DIR:$PYTHONPATH

exec ../bin/gunicorn ${DJANGO_WSGI_MODULE}:application \
  --name $NAME \
  --workers $WORKERS \
  --user=$USER \
  --group=$GROUP \
  --bind=$BIND \
  --log-level=$LOG_LEVEL \
  --log-file=-

Make the gunicorn_start file is executable:

chmod u+x bin/gunicorn_start

Create a directory named run, for the unix socket file:

mkdir run

Configure Supervisor

Now what we want to do is configure Supervisor to take care of running the gunicorn server.

First let’s create a folder named logs inside the virtualenv:

mkdir logs

Create a file to be used to log the application errors:

touch logs/gunicorn-error.log

Create a new Supervisor configuration file:

sudo vim /etc/supervisor/conf.d/urban-train.conf

/etc/supervisor/conf.d/urban-train.conf

[program:urban-train]
command=/home/urban/bin/gunicorn_start
user=urban
autostart=true
autorestart=true
redirect_stderr=true
stdout_logfile=/home/urban/logs/gunicorn-error.log

Reread Supervisor configuration files and make the new program available:

sudo supervisorctl reread
sudo supervisorctl update

Check the status:

sudo supervisorctl status urban-train
urban-train                      RUNNING   pid 23381, uptime 0:00:15

Now you can control your application using Supervisor. If you want to update the source code of your application with a new version, you can pull the code from GitHub and then restart the process:

sudo supervisorctl restart urban-train

Where urban-train will be the name of your application.


Configure NGINX

Add a new configuration file named urban inside /etc/nginx/sites-available/:

sudo vim /etc/nginx/sites-available/urban-train

/etc/nginx/sites-available/urban-train

upstream app_server {
    server unix:/home/urban/run/gunicorn.sock fail_timeout=0;
}

server {
    listen 80;

    # add here the ip address of your server
    # or a domain pointing to that ip (like example.com or www.example.com)
    server_name 107.170.28.172;

    keepalive_timeout 5;
    client_max_body_size 4G;

    access_log /home/urban/logs/nginx-access.log;
    error_log /home/urban/logs/nginx-error.log;

    location /static/ {
        alias /home/urban/static/;
    }

    # checks for static file, if not found proxy to app
    location / {
        try_files $uri @proxy_to_app;
    }

    location @proxy_to_app {
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header Host $http_host;
      proxy_redirect off;
      proxy_pass http://app_server;
    }
}

Create a symbolic link to the sites-enabled dir:

sudo ln -s /etc/nginx/sites-available/urban-train /etc/nginx/sites-enabled/urban-train

Remove NGINX default website:

sudo rm /etc/nginx/sites-enabled/default

Restart NGINX:

sudo service nginx restart

The Final Test

Alright! At this point your application should be up and running! Open the web browser and access it:

Urban Train Deployed

A final test I like to run is rebooting the machine and see if everything restarts automatically:

sudo reboot

Wait a few seconds. Access the website via browser. If it loads normally, means everything is working as expected! All the process are starting automatically.


Updating the Application

Usually that’s the process you will follow when you want to update your Django application:

ssh urban@107.170.28.172

source bin/activate
cd urban-train
git pull origin master
python manage.py collectstatic
python manage.py migrate
sudo supervisorctl restart urban-train
exit

Caveats

I just wanted to take you through the basic steps of a deployment using Digital Ocean. Actually, for the most part the basic steps will be the same for pretty much every cloud provider.

Please note that there are lots of details regarding security, performance, how you should manage the sensitive data of your application, SSL certificate installation and so on.

I highly recommend reading all the available documentation about deployment on the official Django Docs. Also it’s very important to learn about the tooling you are using. Gunicorn, NGINX, Ubuntu, Supervisor, etc.

Further reading:

I hope you enjoyed this tutorial! If you have any question, please leave a comment below!


DigitalOcean $10 free credit to get started.

Even though I’m sharing a referral link, I truly recommend their service (this blog itself is hosted there) and I have several Django applications deployed on DigitalOcean. I find it very inexpensive and ridiculously easy to setup.