#18 Django in Production - From Zero to Hero - Part-1

You have your Django application ready. It is time to deploy it. This lesson (and following ones) will guide you with planning, detailed explanation of Django application deployment walk-through and actual commands/steps. As bonus we will learn to automate it with ansible (de facto standard tool for automation).

I will skip the obvious - don't run Django in production with built-in web server, don't use DEBUG=True in production... you already should have heard that many times.

There are couple of scenarios to consider, for example deploy database on different computer or use S3 as media storage or deploy static files to some CDN. But the truth is, that even the absolute minimum deployment with everything (database, application, static files) located on single computer is complex enough for beginning, why complicate it more?

So, in this guide, we will aim to move our Django based project on single remote computer. The anatomy of simplest Django application in production will look like in picture below.


Our production toolset:

  1. Nginx (web server)
  2. PostgreSQL (database)
  3. Django (the main protagonist)
  4. Gunicorn (application server)

The numbered order corresponds to the order in which we will install them. In this lesson we not going through any of those 4 points. In this lesson we will take care of something, which needs care - even before you start whole deployment adventure. We will take care to setup of our SSH right way.

SSH with Public Key Authentication

On your rented VPS you have either root access or maybe some other privileged user. You must make sure you have public key authentication instead of password based. There two reasons for this:

  • It is more secure
  • It saves you time on typing password every time you login

Also you may consider to configure your ssh to alias your server (I will explain below what I mean); boths of these steps are necessary because you will be SSHing a lot into remote machine, typing one character less will make a big difference. So, instead of:

$ ssh ubuntu@     # and then password
$ ssh root@mydomain.com # and then password

You will type:

$ ssh u  # ! right, just letter 'u' and no password; it will use public key authentication instead

At this point you are root user logged into remote machine. I will skip morale talk why you must not run commands as root. Instead, create another user and add it to sudo group.

root@localhost:~$ useradd -m -s /bin/bash -G sudo john  # add user john, with home directory and with bash as default bash and include him in sudo group.
root@localhost:~$ passwd john   # set password for john user

Double check that user john was created and he belongs to sudo group:

root@localhost:~$ cat /etc/passwd   # user john will be listed at the bottom of the file
root@localhost:~$ groups john  # should list two groups: john and sudo

So far so good. But we still don't use public key authentication for user john. Let's solve that first. The idea is very simple, we create on your local computer a pair of files, one is secret file (private key), and the other one you can share with everybody (public key). We will copy public key file to remote host (using ssh-copy-id command) thus telling remote host to allow log in of any user with private key which corresponds to this public key.

$ ssh-keygen -f ~/.ssh/johnk   # generate public/private keys named identified as johnk, to ~/.ssh folder
$ Generating public/private rsa key pair.
$ Enter passphrase (empty for no passphrase): # leave passphrase empty
$ ssh-copy-id -i ~/.ssh/johnk john@<remote-ip>  # copy public key identified with johnk to remote machine john@<remote-ip>

You will be asked password for user john from remote host. And then you will be able to ssh john@<remote-ip> without typing password (ssh will authenticate your account based on your private key).

And last step, which is extremely important - is to disable password based authentication on the remote server. Edit /etc/ssh/sshd_config. Change following settings:

PubkeyAuthentication yes
PermitRootLogin no
# Disable password based authentication
PasswordAuthentication no
ChallengeResponseAuthentication no
MaxAuthTries    2

Restart you sshd server with sudo systemctl restart sshd for changes to take effect on the server.

Type Less with SSH Host Aliasing

And finally, create a file ~/.ssh/config on your local computer with following content (i.e configure your local ssh client):

Host jo
    ForwardAgent yes 
    Hostname <remote-ip>  # replace with actual IP
    User john
    IdentityFile /home/<whatever-user-your-local-machine-has>/.ssh/johnk

Above ssh configuration is really cool, it enables you to just type ssh jo and ssh will login you into remote host john@<remote-ip> without asking password! Obviously instead of jo can be any name you like. Even more, if you use more remote machines, you can configure something like this:

Host *
    IdentityFile /home/<whatever-user-your-local-machine-has>/.ssh/johnk
     ForwardAgent yes

Host jo
    Hostname <remote-ip>  # replace with actual IP
    User john

Host db
    Hostname <ip-or-dns-of-db-server> 
    User johndb

Host ap1
    Hostname <ip-or-dns-of-some-app-server> 
    User appuser

Didn't we plan to deploy a Django application ? Sure! This is our goal, but don't rush. There are many steps in between and each step needs to be executed properly. We just accomplished one step, but extremely important one - we configured our local ssh client. Now, let's give remote machine a proper name. We will name remote machine - john-systems. To accomplish this, use systemd's command hostnamectl

$ sudo hostnamectl set-hostname john-systems

If you log out and log in again (or alternatively enter a new bash by typing... bash), you will notice that prompt changed to: john@john-systems.

Now, let's take care of computer DNS record. Instead of referring to it by IP we will give it a DNS name. For that, you need to have a domain registered with registrars like namecheap. I will use as example my own domain dglte.net. You need to add a so called A Record (via web interface of your registrar). In my example I will map production-demo.dglte.net to external IP address of my VPS server . When done, you will need to way up to 24 hours for DNS server to update. At any point in time, you can use dig command to check if A record maps to your IP address:

$ dig production-demo.dglte.net -t A

When DNS records were updated, dig command's response will be something like:

Wow, no similar lessons found! You have just found a very unique lesson!