Your First Django Heroku App

I few weeks ago I decided to rewrite an old PHP site in Python + Django, and settled on hosting it on Heroku, as I didn’t have time to go through the rigmarole of setting up my own VPS just to demo the site. Heroku is a fantastic platform for app hosting but there are a few gotchas that I ran into on the way which I thought I’d share.

A quick overview of the platform, then, in case you’re not familiar with it – Heroku is a cloud hosting service for applications, rather than servers, and supports several software stacks, Python among the newest added. It plugs into your git workflow so you can deploy with a simple git push, and your app gets compiled down into a runtime known as a slug which Heroku serves from its network of app servers. Nearly all configuration/management of your app is done from your local command line via the herokutoolbelt” app. The pricing structure is such that you can run a simple app with a small database for free.

TweetVault desktop screenshot

Your application slug is read-only, and each of Heroku’s web server processes (known as “dynos”) runs in its own isolated environment so your app itself must be stateless – you can only persist stuff to database, anything else (e.g. uploaded media files) must be written to and served from another environment, such as S3. You can serve static files (e.g. JS, CSS, images) from your app, but you won’t be able to change them without doing a new deployment.

The first issue I encountered: Heroku detects your app’s software stack by looking for several common files, and in the case of Django apps it’s looking for settings.py. However in my Django project structure I like to maintain separate configs for common, local and production in separate directories, so I had to explicitly tell Heroku where to find my settings by setting the DJANGO_SETTINGS_MODULE environment variable thusly: $ heroku config:add DJANGO_SETTINGS_MODULE=myapp.config.heroku.settings (replacing myapp with your app name)

Heroku expects your Python app to use virtualenv, and also pip to install and manage packages.

I found that in order to run my app scripts on the remote side I also needed to update my Python path, which requires setting another environment variable: heroku config:add PYTHONPATH=/app:/app/myapp

Next I needed to set up what Heroku calls the “shared database” addon, which is basically a 5MB free PostgreSQL instance that your app can have access to. It took me a while to figure out that this isn’t provided by default, you have to enable it by running heroku addons:add shared-database . Heroku addons provide their access details via environment variables, so once you’ve enabled the shared database you’ll find (via heroku config, which lists them) that you’ve got a new variable called SHARED_DATABASE_URL containing the URL and login details for your app’s PostgreSQL db (you also need psycopg2 installed, via pip install psycopg2).

Because Heroku’s stack detection magic didn’t like my app structure, I found I also needed to manually add some bits to my settings file to introspect the environment and set up the database connection based on the SHARED_DATABASE_URL var. The code is here (at the bottom). A normally-structured Django app will have this code automatically added during deployment, but mine didn’t.

I also had to manually tell South (the migration library I was using) to use the PostgreSQL backend, by adding the following to my production settings file: SOUTH_DATABASE_ADAPTERS = { 'default': "south.db.postgresql_psycopg2" }

The final big obstacle came when deploying – I run collectstatic on my Django apps to gather all my static files together into one location, and this wasn’t working on Heroku. It turns out that the collectstatic command runs in its own virtual environment, separate from the one in which the web server dyno runs, so the latter never has access to the collected files (the application slug is read-only, remember?). I found this helpful link on the subject, which explains how to combine the collectstatic and gunicorn start commands into one in your Procfile (which tells Heroku how to run your app) so they share an environment. Check the comments on that post for details on how to adjust your urls.py file to properly point to the static files too.

Worth noting that in that previous link, the Procfile command to start gunicorn refers to a $PORT var – this is present in the web server dyno environment: you have to bind to the port they specify, or nothing will work – but this is pretty much handled for you.

To get media files working properly and serving from S3, I used django-mediasync to copy my local media files to my S3 bucket (one time only), then used these settings in my production settings.py to hook it all up, via django-storages and boto. Note the last line, which instructs easy_thumbnails (if you’re using it) to use the same storage engine as the app default. Without this, easy_thumbnails won’t be able to save generated thumbnails to S3. Took me a while to diagnose that one.

A couple of other bits: if you want to send email from your app, you’ll need to use your own server or use the free Sendgrid addon which allows up to 200 messages a day, beyond which you’ll need to hand over some cash money. Heroku don’t provide outgoing email facilities themselves.

Lastly, it’s worth noting that Heroku’s architecture will automatically put a running app into a sleep state if it’s not accessed for a while. This can have the effect of making the first request in a while quite slow. I got around this by setting up a free Pingdom account to ping my app every few minutes, which keeps it alive.

Hope this sheds a little light on some of the less-documented aspects of running Python/Django apps on Heroku. It’s a great platform and I found getting to grips with it to be a really useful learning experience. Good luck!