This week I finally deployed the KidTasks app to Heroku. While I was very happy with Heroku and how it behaved, I’ll have to admit that many of the shortcuts I’ve taken in developing this first app have come back to bite me during this process. None of the issues are things I can blame Heroku for. They were all self-inflicted.
To get started, I walked through the demo application instructions on Heroku and deployed the demo app they have. I thought this demo was really well put together and explained things quite well. Unfortunately, there were several requirements that I hadn’t yet met. Fortunately, most of those were easily solved.
I had only been running the app locally and using the built-in Django server. This is great for what it is, but not recommended for an actual deployment.
The first several changes were fairly trivial. They recommend using gunicorn; I’ll use gunicorn. pip install; pip freeze; git add requirements.txt and that’s all set.
Similarly they recommend dj_database_url for reading the database settings out of the environment. Also easily installed and the code modifications they showed worked without issue.
The White Album
The next suggested package was WhiteNoise which manages serving static content from Django. I honestly had never thought of this before and had some learning to do. The install was fine, but I ran into issues with the bootstrap fonts not being present during the collectstatic operation. It took me a bit of digging but I figured out a solution for this – I put the bootstrap fonts in the static/bootstrap/fonts directory. I’m not sure if this is the preferred solution, but it works for now and keeps all the files local.
At this point I attempted my first deploy. Of course, it failed hard.
But the logs were kind enough to point out the problem:
CommandError: You must set settings.ALLOWED_HOSTS if DEBUG is False.
and, doing some searching on the web and the example app, the answer was easy, simply set the field to * in settings.py
# Allow all host headers ALLOWED_HOSTS = ['*']
It was also at this point that it occurred to me that I had to make other changes to my settings.
Do You Want To Know A Secret?
Setting the debug flag to False was an obvious change in settings, but what to do about that pesky secret key? Heroku (and the Two Scoops book) both recommend using an environment variable, but several sites on the web point out that environment variables are less secure than a file on the filesystem. While this may be true in general (“cat /proc//environ” on a linux box will give you the environment variables), it doesn’t appear to be on Heroku’s dynos.
Another idea was to use a dynamic secret key for the app. This can be done with code (from SO) that mirrors how Django initially creates these keys:
from django.utils.crypto import get_random_string chars = 'abcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*(-_=+)' get_random_string(50, chars)
While this would work for this instance, there are some problems with it and it seems like using “best practices” as general rule would be a good idea, so I followed the directions and set it in the environment.
Next came the big challenge. I had been using sqlite3 for my app because, well, it’s easy. Heroku doesn’t support sqlite3 as explained in the first answer in this SO post.
So, I needed to switch to a new database. As postgres was the recommended solution and I didn’t have another preference, I went with that.
This conversion actually took a good portion of the entire process, again, largely through self-inflicted problems.
The basic setup for running postgres on Linux I gleaned from this site.
The dj_database_url and heroku’s default database config settings were fine. There were just a couple of things I needed to learn first. I needed to set up the postgres database with a full user, grant rights, etc. Doing this locally, it took me a while to figure out all the the steps. But, once I had done this, transferring that to Heroku was very straightforward. You can to
heroku run bash
to get into the dyno, or for this case
to get you directly into the postgres prompt on the production database.
The final hurdle turned out to be with the database. I had forgotten that you need to specify the app name when doing makemigrations and migrate. I kept getting errors starting the app because it couldn’t find any of the required tables for the app. It turns out that
heroku run python manage.py makemigrations heroku run python manage.py migrate
acted like they succeeded but didn’t create the required tables. After a bit of late-night head scratching, it finally dawned on me to add the name of the app
heroku run python manage.py makemigrations tasks heroku run python manage.py migrate tasks
Once I got that solved the app was up and running.
Full Speed Ahead, Mr. Boatswain!
Mainly because I will likely find it handy in the future, I’m going to list some of the commands I found useful in the adventure.
- heroku local web – test the configuration on your local system
- heroku create kidtasks – create a new heroku app with a name
- git push heroku master– this triggers a full deploy of the code
- heroku config:set SECRET_KEY=<my super secret key>– sets environment variables on the app’s dyno
- heroku run python manage.py [makemigrations tasks | etc] – basic django commands on the deployed app
- heroku logs –tail – get the logs from the app
While this process took more effort than I expected, I don’t think Heroku is to blame for any of it. Now that I have a working example (and an idea of how postgres works), the next time will be much smoother.
Next app I start, I will definitely be using this template and see how much time it saves me.