Continuing from where we left off, we’re actually going to go and deploy our app! Exciting stuff. Let’s go through the checklist:

Switch away from manage.py runserver

I already have nginx running on my VPS, so we just need a bridge between that and Django’s WSGI. uWSGI seems fine and there’s both a DigitalOcean guide and an official uWSGI tutorial specifically deploying Django available online.

Achtung, the DigitalOcean guide uses Python 2! (This doesn’t really affect our actual deployment, since the Django-generated wsgi.py is obviously Python 3, but the example ‘bare minimum’ Python wsgi application won’t work – need to do return [b"<h1 style='color:blue'>Hello There!</h1>"] instead of return ["<h1 style='color:blue'>Hello There!</h1>"].)

Side note: uWSGI initially warned me that !!! no internal routing support, rebuild with pcre support !!!. This was remedied (as per Stack Overflow post #21669354) by apt installing libpcre3 and libpcre3-dev, then running pip install uwsgi -I --no-cache-dir.

Alright, we’ll create an ini configuration file and put the Django-recommended configuration values in there. We’ll refine it later if we have to…

If we use a Unix file socket as the IPC between nginx and uWSGI, we need to make sure nginx has permission to read to and write from the file. After many frustrated attempts to start the uWSGI process under the nginx account (learning both (1) how to setuid on a file and (2) that setuid doesn’t work on shell scripts) I gave up and just added the nginx user to my main user account’s group on my VPS, which works fine. I really need to do a proper security assessment of my VPS at some point and sort out the user permissions situation, but I digress.

Domain names, SSL/TLS, and the external world

Let’s swing by my domain name provider, shall we?

TODO:

  • Fix/polish nginx config file (split into different config files for all of the different services?)
  • Figure out if it is really nftables that’s blocking all non-standard HTTP(S) ports
  • Set up Certbot so I don’t have to keep replacing my SSL certificates manually every three months

“Visiting the live Django site from a regular web browser at an actual domain name” now works despite all of the above.

Critical settings

SECRET_KEY - generated a new secret key with get_random_secret_key() and put it in a .env file. We now need to export the contents of the .env file to the environment variables of the uWSGI process when we run it so that the Django settings file can load the SECRET_KEY from the environment variables. I do this by starting the uWSGI from a shell script that looks like:

#!/bin/bash
source .venv/bin/activate
export $(grep -v '^#' unflaky-web/.env | xargs)
uwsgi --ini unflaky.ini

(cribbed from Stack Overflow post #19331497)

DEBUG - set to False

Environment-specific settings

ALLOWED_HOSTS - set to my domain name

After setting ALLOWED_HOSTS our site now actually works. Well, except for the static files.

CACHES, EMAIL_BACKEND - ignored for now, no caches and we don’t send mail

DATABASES - ignored, I’m just using sqlite for now. This is how professional web development is done at the highest level

Static files

This was actually super simple, I just picked a place for static files to go (STATIC_ROOT) then run python3 manage.py collectstatic.

Error reporting

Putting a 404.html file in the “root template directory” just means putting it in a directory called templates in the root of your project directory (i.e. where manage.py is) and adding an entry to TEMPLATES in settings. See LearnDjango’s article on Customizing Django 404 and 500 Error Pages.

Upshot

We did it! We’re live! Now we just need to implement actually useful functionality!! See you next time for when we do that!!!