Deployment

@dokku @deploy @production @server

This section covers how to deploy the website onto a VPS. There are a few different options here, from manually configuring Apache and WSGI, to using a PaaS like Heroku and Dokku.

Deploying with Dokku

Dokku is nice because once set up, you just push updates to the repo on your server and it all happens pretty automatically. However setting it up is a pain in the ass.
Also, the bible: https://cheat.readthedocs.io/en/latest/django/dokku.html

TIPS:

  • Make sure your migrations are in your repo. Do not exclude them
  • You can open a bash shell to the running container by doing: dokku enter <appname> where appname is your DOKKU app name, NOT THE LOCAL DJANGO NAME
  • Run any command using dokku run <appname> <command> e.g. dokku run mtnpak python manage.py collectstatic
  • You can see a live view of the dokku logs by doing on the server: dokku logs <appname> --tail
  • Setting up the PostgreSQL database is kind of a pain. Make sure your settings.py is correct

1) Installing Dokku

Installing is pretty easy, You can check on the official repo, or just follow these instructions:

$ wget https://raw.githubusercontent.com/dokku/dokku/v0.21.4/bootstrap.sh
$ sudo DOKKU_TAG=v0.21.4 bash bootstrap.sh

Replace the value for DOKKU_TAG with the current version

2) Adding SSH keys and allowing host

You need to now visit the ip address of your remote machine in a browser, and in the Dokku form, paste your SSH public key.
Then you must allow the dokku user to log in via ssh:

$ sudo nano /etc/ssh/sshd_config

# Add the dokku user to the following line
AllowUsers faaiz dokku

Finally, as of writing this (August 2020) Dokku is weird in that it does not correctly set up the keys. I think this has something to do with the fact that I already use the same key for the faaiz user when I set up the VPS. Either way, you need to first check if dokku has any keys, then use dokku to add them if not:

# Check if there are any keys
$ dokku ssh-keys:list

# Chances are you'll get an error like:
/home/dokku/.ssh/authorized_keys line 1 failed ssh-keygen check.
# or
No public keys found 

# So what you need to do now, is delete that authorized_key file for the dokku user if there is one, copy your public key to some location on the server, and point dokku to it. You can pick your own key name
$ sudo dokku ssh-keys:add <name_for_key> /path/to/key

# Example of what I did
$ sudo dokku ssh-keys:add fa0 /home/faaiz/fa0-ssh-key

Now you can test if this worked by seeing if you can SSH as the dokku user:

$ ssh dokku@<your_server>

# Example: I've set up my sshd_config to use port 333 for ssh, and use the hostname fa1

$ ssh dokku@fa1

# Output should be a bunch of dokku related help messages, then connection closes automatically. If this happens, then your SSH is working.

3) Creating Dokku app and PostgreSQL database

Create a Dokku app and install the plugin for postgres

# Create app
$ dokku apps:create <appname>

# install postgres plugin
$ sudo dokku plugin:install https://github.com/dokku/dokku-postgres.git

# create a db
dokku postgres:create <dbname>

# link db to app
dokku postgres:link <dbname> <appname>


4) Configure domains and add repository

In your server running Dokku, you want to add your domains for some reason (I don't understand this, since I do my domain stuff on Vultr DNS) but in any case, this is how they want you to do it:

$ dokku domains:add <appname> subdomain.domain.com # I think I can just do domain.com to serve from the root domain

Then you need to go to your local machine and add your remote repository. You will push to this when you want to deploy the website.

> git remote add dokku dokku@<host_name>:<appname>
You can call the repo whatever you want (in this case dokku) but the user HAS to be dokku

IMPORTANT: If you are using a subdomain, make sure to add .domain.tld to your Django ALLOWED_HOSTS to tell it to accept serving from a subdomain. Otherwise it will not. You also need to add domain.tld without the . if you are just trying to serve it from the root domain.

5) Add the correct buildpacks

You need a buildpack for python and for GDAL. For some reason, the documented way doesn't work for me. What I need to do is create a file called .buildpacks in the root of my local project repo, and add to the contents:

https://github.com/heroku/heroku-geo-buildpack.git
https://github.com/heroku/heroku-buildpack-python.git

6) Set up the Procfile

In the root of your local project directory, create a file called Procfile and add to its contents:

# This is the WSGI setting for Gunicorn. Make sure 'MTNPAK.wgsi' is the path to your wsgi.py in dot form, i.e. '/' replaced by '.'
web: gunicorn MTNPAK.wsgi:application

# This runs commands during deploy: make migrations, migrate db, and then load the grades fixture data
release: python manage.py makemigrations; python manage.py migrate --noinput; python manage.py loaddata grades.json

Load whatever else fixtures you need here.

7) Create your Django superuser

Easy enough. Once container is running, do:
dokku run <appname> python manage.py createsuperuser

8) Set up persistent storage for media

http://dokku.viewdocs.io/dokku/advanced-usage/persistent-storage/
IMPORTANT:

When DEBUG=False in your settings.py you need to manually configure nginx to correctly serve your media files in the container. Copy the contents from here and paste them into a file called nginx.conf.sigil in your project root (where Procfile is). Look for loops with http and https conditionals, and before the first location / {...} statement for both http and https (where / means site root, add another location statement:
location /media/ {
alias /var/lib/dokku/data/storage/<appname>;
}
This will tell your nginx to serve files at yoursite.com/media from the path on the server (NOT container) that you want (in this case /var/lib/dokku/data/storage/<appname>

Django settings configurations as follows:

MEDIA_ROOT = '/storage'
MEDIA_URL = '/media/'
Dokku's default storage mount point in the container is /storage and I have no reason to change that
Then, on the server, you want to create a folder for your app in:
$ mkdir -p /var/lib/dokku/data/storage/<appname>
Then, you must set the permissions so that dokku and your container have access (32767 is the default container group id if you are using buildpacks:
$ sudo chown -R dokku:dokku /var/lib/dokku/data/storage/<appname>
$ sudo chown -R 32767:32767 /var/lib/dokku/data/storage/<appname>
Then just mount the storage. This command mounts your server storage directory to a folder in the container /storage.
$ dokku storage: mount <appname> /var/lib/dokku/data/storage/<appname>:/storage
Check that your storage is mounted correctly, then restart the container
$ dokku storage:list <apname>
$ dokku ps:rebuild <appname>
This storage should remain mounted to the app unless you destroy and redeploy your server


9) Configure Whitenoise to serve static files

DO NOT DO THIS ON HIGH TRAFFIC WEBSITES
http://whitenoise.evans.io/en/stable/django.html
This is a cheap (free) option to let Django serve static files. Obviously, you need to pip3 install whitenoise first and foremost. Then follow the steps below and make changes in settings.py.

You need to make sure that STATIC_ROOT is properly configured in your settings.py :

STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')
And all your static file loading should be using the {% static ... %} template tag.

Then run manage.py collectstatic to collect all your static files to the staticfiles folder

Then add 'whitenoise.middleware.WhiteNoiseMiddleware' to your middleware (should be second in line behind SecurityMiddleware

Add STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage' for compression and caching OR
STATICFILES_STORAGE = 'whitenoise.storage.CompressedStaticFilesStorage' for just compression.

Then, you need to make it such that whitenoise serves static files even when you are using your runserver in development. Do this by adding the following to the TOP of your INSTALLED_APPS :

'whitenoise.runserver_nostatic'
Easy pease.

10) Add SSL certificates

https://github.com/dokku/dokku-letsencrypt
First, install the letsencrypt plugin:

$ sudo dokku plugin:install https://github.com/dokku/dokku-letsencrypt.git
Then set your email for the certificate:
$ dokku config:set --no-restart <appname> DOKKU_LETSENCRYPT_EMAIL=<email>
or, for global email setting, do:
$ dokku config:set --no-restart --global DOKKU_LETSENCRYPT_EMAIL=<email>
Finally, set auto-renewal of certs by doing:
$ dokku letsencrypt:cron-job --add

DEPLOY WITH 'git push dokku master:mater'