I finished building the first version of this site a couple of weeks back. But, I hadn't a mote of a clue how to deploy a site. So I went a-googlin'. I turned up this article: "How To Set Up Django with Postgres, Nginx, and Gunicorn on Ubuntu 16.04". It looked encouragingly complete. But you know how the story goes. It doesn't simply go. It stumbles all over the place, falls off a few cliffs, drowns, and then you do it again. And again, and again, and again ad infinitum ad naseam.
You might want to read this piece if you're a beginner coder like me who has realised that tutorials and documentation leave out a lot of stuff. This is not a full tutorial. It's a list of things that tripped me up a lot while I was learning to deploy a site. I hope it helps.
Some of the things I had to become familiar with before things became smoother were, how to:
Use ssh key pairs for user authentication. This took some practice!
I come from a Windows background. I had to not only learn but become familiar with bash commands so that I could work properly. Some of the commands I found invaluable when I started:
ls - list files in directory.
echo $PWD - display the present working directory to screen.
sudo - run command as superuser.
which [program] - find out where the program is located.
ls -sh [file/folder] - find out the size of the file or folder.
source [file] - execute the commands in the file, for example, to activate virtualenv.
touch [filename] - create a file called filename.
Set up a Python 3 virtual environment.
Not knowing that I should do this tripped me up a lot initially. Gunicorn and other packages kept meeting with errors. I investigated and found that they were caused by my use of Python 3 and Ubuntu's Python 2.7. I spent a lot of time installing and uninstalling Pythons 2.7 and 3 and trying to make Ubuntu use Python 3 instead, but there errors kept coming.
Finally, I learnt to be strict about using Virtualenv to set up Python 3, and everything worked. However, instructions on most tutorials tend not to mention how to set up a virtual environment specifically for Python 3. Here's the code: virtualenv --python=/usr/bin/python3 your_env_name
This was super confusing at first. Nothing I pressed on the keyboard worked. Then, for some reason, it did. No idea what I pressed. The tutorials don't usually tell you either. Some googling and practice later, I got it. Here are some very important commands:
sudo vim [file] - open the file with vim as a superuser so you can definitely edit it.
s (when you first open the file) - I think it's called insert mode but it might be a bit more complicated than that. Anyway, it lets you start typing.
Escape key - brings up the : at the bottom of the screen where you can enter your next command.
w (after pressing the escape key) - write to file. That is, save the file.
q - quit vim.
wq - write to file and then quit vim.
! - adding this to w or q or another command forces it to be executed.
/ - find a string in vim.
Edit gunicorn and nginx configurations, and how to restart them.
And then while reading Two Scoops of Django, I remembered I had to deal with security. So, how to:
Get SSL certificates for HTTPS.
This was also harder than expected. The challenges kept failing at first, for reasons I've forgotten. There were probably several of them. Maybe the A and CNAME records were not right, or the nginx configuration was wrong. In any case, the problems led me to hunt around for solutions and I found out that there's an nginx plugin for certbot. But, it didn't work.
I somehow managed to get certbot working with its standalone mode, but that seemed to mean I couldn't use it to renew the certs automatically. Two DigitalOcean tutorials had slightly different things to say about renewal. "How To Secure Nginx with Let's Encrypt on Ubuntu 16.04" says it'll work by itself. "How To Use Certbot Standalone Mode to Retrieve Let's Encrypt SSL Certificates" says you have to add a renew_hook. I left it as it is for now. Too tired to figure it out right now so I'll have to get back to it later.
Then-- and this is super important-- I realised that I shouldn't be getting SSL certs willy-nilly when testing out deployments. There are rate limits! Nobody mentioned this. Noobs can't know this! Noobs don't even know best practices for development, staging, and production environments. I got a bit worried at this point because I thought I must be damn near deadly close to breaking the limits with my constant and unceasing iterations every single day. Anyway, the solution is to use the --staging flag with certbot.
Don't forget to reduce its max-age to very, very, very little when you're only testing things!
Enable a bunch of settings in settings.py for various kinds of nasty circumstances. These include:
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
CSRF_COOKIE_HTTPONLY = True
X_FRAME_OPTIONS = 'DENY'
SECURE_CONTENT_TYPE_NOSNIFF = True
SECURE_BROWSER_XSS_FILTER = True
SECURE_SSL_HOST = True
SECURE_SSL_REDIRECT = True
SSL_FORCE_URL_PREFIXES = False
SSL_FORCE_HOST = 'example.com'
In particular, I thought SSL_FORCE_HOST required a value of True or False. It was only by accident that I saw one example online using a URL.
Along the way, I realised that:
Let's Encrypt doesn't have SSL certs for wildcard subdomains. (Coming soon?) Not knowing that wildcard subdomains require their own certs caused me to spend too much time trying to figure out why I couldn't simply redirect requests for them to the main domain in the nginx configuration. Luckily, someone told me about it.
I had to pay attention to the domain's Time To Live (TTL).
I had to remember to delete the A and CNAME records for the domain every time I deleted the droplet because otherwise it'd send visitors to someone else's website when the IP is reused.
The postgres user must have the same username (and password too? I'm not sure now) as the Ubuntu user in order to set up or edit the database. This was a little bit hard to figure out.
I also had to deal with spam and analytics. The documentation for the Akismet spam filter for Mezzanine is particularly thin. I did not know whether to add the key and ID to the admin panel or to settings.py. The funny thing is, once I added them to settings.py, they disappeared from the admin panel. Is that supposed to happen? I don't know.
And, I found out through the thin literature that I do have to add Google Analytics' code to every page I want tracked. I used these examples on GitHub as a guide.
In addition to all these things, I was deploying new iterations of these configurations several times over on almost every single day. At some point, I got quite tired of it. Looking around for a solution turned up this practice called configuration management, for which the most suitable one seems to be Ansible. However, I couldn't learn Ansible while learning all these other things. So I defaulted to using what I knew: the win32gui package. Essentially, I wrote a script that allowed me to press enter on my shell on Windows to send commands to my Cloud9 Ubuntu shell.
Oh, did I mention I was using Ubuntu on Cloud9 for deployment? I'm on Windows, and Mezzanine conflicts with it regarding the use of backslashes or something. I can't remember. So I moved to Cloud9 for the last bits of development. I wanted to use VirtualBox but it didn't work for me. I still have to troubleshoot that. Anyway, I used the shell on Cloud9 to rsync the Mezzanine directory over to my DigitalOcean droplet, then I did an ssh into the droplet and configured things from there. Eventually, my web32gui script could handle everything from the initial login to the droplet to the rsyncing and all the way through to setting up gunicorn, nginx, SSL, and HSTS. I hope to get to Ansible soon.