Django/Heroku quickstart for existing applications

Getting a brand new Django application running on Heroku is fairly simple. There is a great tutorial for it on devcenter.heroku.com. I would recommend going through that, and getting it working. Here is my short version of their getting started notes, without the virtualenv and Django command stuff you probably already know.

wget -qO- https://toolbelt.heroku.com/install.sh | sh
heroku login
heroku create --stack cedar
echo 'web: python manage.py runserver 0:$PORT' > Procfile
heroku addons:add shared-database
git push heroku master  # if you're in a branch locally, you can git push -f heroku localbranch:refs/heads/master
heroku run python manage.py syncdb
heroku ps:scale web=1
heroku ps
heroku logs

That should be enough to get a brand new Django app running. If you have an existing Django application, however, their documentation doesn't provide much guidance on common pitfalls.

First of all, you may need a mechanism to over-ride settings.py on the production Heroku instance. First, you can add an environment variable:

heroku config:add ENVIRONMENT=production

With that variable, you can easily create an over-ride for settings.py called settings_production.py:

# at the END of settings.py:
import os
ENVIRONMENT = os.environ.get('ENVIRONMENT', 'dev')  # dev, production, qa, etc
exec('from settings_%s import *' % ENVIRONMENT)

Heroku gives you a shared database for free. You can see the connect string on it with the heroku config command:

...
DATABASE_URL => postgres://foobar:password@ec2-ip-address.amazonaws.com/instance
...

This would translate into the following in settings_production.py

DATABASES = {
    'default': {
        'ENGINE': 'postgresql_psycopg2',
        'NAME': 'instance',
        'USER': 'foobar',
        'PASSWORD': 'password',
        'HOST': 'ec2-ip-address.amazonaws.com',
        'PORT': '',  # leave blank
    }
}

The Heroku directions for switching from Django's development web server to gunicorn don't work for Django 1.3, where there is no WSGI file. After adding gunicorn to requirements.txt, and 'gunicorn' to INSTALLED_APPS, I changed Procfile to the following:

web: python manage.py run_gunicorn -b 0.0.0.0:$PORT

Getting Django's staticfiles feature working was a little trickier. Heroku gives you a "ephemeral" file system, meaning that any changes you write to disk will not survive the next deploy, and may not even survive between heroku command-line sessions. This means that collectstatic must be run as part of the deploy, which you can also do in Profile:

web: python manage.py collectstatic --noinput; python manage.py run_gunicorn -b 0.0.0.0:$PORT

Lastly, I wanted gunicorn to (at least temporarily) serve up the static resources itself. To do that, you will need to add the following to your urls.py:

urlpatterns += patterns('',
    (r'^static/(?P<path>.*)$', 'django.views.static.serve', {'document_root': settings.STATIC_ROOT}),
)

Heroku gives you SSL for free, at least when you're on *.herokuapp.com. The only tricky bit is that you need to tell Django which header to look at to determine if the original request was in SSL (Heroku will only speak plain HTTP to Django). Without this, any redirect from an HTTPS URL will direct the user back to HTTP. Just put the following in settings.py:

SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')

Finally, I had to migrate my old data. If you're going from Postgres -> Postres, or MySQL -> MySQL, just use the herokup database restore tools. If you need to migrate from one to the other, things get a lot trickier. In my case, I ended up exporting to a JSON file, and re-importing. This was very buggy; I would not recommend it.