Deployment or bust, part 4: Actually deploying
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 install
ing 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!!!