Migrating a Django app to Heroku

Heroku supports Python! What are we waiting for?

As a Python and Django kind of guy, I had always been jealous of the Ruby on Rails folks. This has nothing at all to do with the framework itself. No, no, no…. Django all the way. It was the Heroku cloud application platform that had me longing.

Yes, I could run my Django application on Google App Engine, but that requires all sorts of hackery and my app ended up an abomination of the original… too unnatural for my tastes.

I sensed a small shift in the Earth’s rotation on Sept 28, 2011. This is when Heroku added support for Python/Django on their platform. I needed to give it a test drive and I was amazed how simple it was. The following is an account of what I did to port one of my existing Django sites to Heroku…

Initial setup

Heroku already provides a decent quick start guide for Python. That’s a great place to begin. Check out the Prerequisites and Local Workstation Setup which will get you up and running quickly. It helps if you’re already familiar with Git, virtualenv and pip. If you’re not, then now is an excellent time to learn!

First things first. Assuming your Django project is already in Git, change directory to the project root. Then…

$ heroku auth:login
... output omitted ...
$ heroku create --stack cedar
... output omitted ...

Believe it or not, we’re almost there!

Database config

Now, my existing app is running a MySQL database. I’m going to use the built-in Postgres DB on Heroku. So I need to update my Django settings…

# Database config
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql_psycopg2',
    }
}

Note, that I didn’t supply any database info or credentials. Heroku auromatically injects this info into your settings.py file. I also need to dump out my current database which I can later import into my new app (this could also be done with fixtures).

$ python myapp/manage.py dumpdata > db.sql

I’m already storing my Python dependencies in a requirements.txt file. If you’re not, you’ll need to create this file at the project root.

$ pip freeze > requirements.txt
$ cat requirements.txt
Django==1.3
feedparser==5.1
gunicorn==0.12.2
lxml==2.3.3
psycopg2==2.4.2
python-dateutil==1.5
python-sunlightapi==1.1.0

Don’t forget to commit your changes! Then push to Heroku.

$ git commit -a -m 'Mods to run on Heroku.'
$ git push heroku master

That second line above is really something to be admired. Not only does it push the code to Heroku’s repository, but then it triggers a real deployment. Heroku automatically copies the files to the stack, installs all the dependencies (via requirements.txt), detects that this is a Django application, and runs the application with “runserver”. Poof! Done.

Well, not quite done. But really that’s bulk of the it and my app is in fact running. You can confirm this by using the “heroku ps” command.

Running your app

Now I need to do all the normal Django setup stuff, like syncdb and loaddata…

$ heroku run python myapp/manage.py syncdb
$ heroku run python myapp/manage.py loaddata < db.sql

If all goes well, I should be able to hit my site with a web browser at the wacky hostname provided by Heroku when I created the stack. Herou provides a shortcut…

$ heroku open

Final thoughts

We’ll that was pretty darn easy. A few other things to note if you’re trying this yourself.

  • You’ll need to serve static media from somewhere. I used django.contrib.staticfiles in this example, but that’s probably not idea for production. Though the output does get cached… so it’s also not too bad.
  • You don’t want to use the built-in Django runserver. I prefer gunicorn and it’s easy to configure that!
  • Enjoy yourself. This is cool stuff!

Dear Congress…

A note to Congress regarding SOPA and PIPA

Dear Representative Van Hollen, Senator Cardin, and Senator Mikulski:

The “Stop Online Piracy Act” (H.R. 3261) and the “Preventing Real Online Threats to Economic Creativity and Theft of Intellectual Property Act of 2011″ (S.968) are intended to solve a worthy problem, yet the methods recommended by these bills I find to be completely offensive.

I greatly value the protection of intellectual property, yet I place the values of freedom and free speech even higher. We must not enact a bill with power to dismantle these very freedoms.

Please find another way. Punish the offender, not the messenger. I have confidence that you will find an acceptable alternative.

Please undertand very clearly that your vote for these bills in their current forms will be countered with a vote against you in your next election.

Sincerely,

Drew Engelson
Cabin John, MD

Custom Django management commands on Heroku

Quick solution to a common Django/Heroku problem.

Running custom management commands

Running Django management commands is easy on Heroku. For example, to syncdb you simply execute:

$ heroku run python your_app/manage.py syncdb

Easy enough. But you may find that running a custom management command to be a little trickier. You might run into something like this:

$ heroku run python your_app/manage.py your_custom_command
Running python your_app/manage.py your_custom_command attached to terminal... up, run.2
Unknown command: 'your_custom_command'
Type 'manage.py help' for usage.

Ouch! And you wouldn’t be alone here:

The solution

This really just comes down to Python not finding your app. You just need to adjust your Python path to include your home directory.

On Heroku, your home directory is generally “/app”. You should confirm this by running:

$ heroku run env | grep HOME
HOME=/app

A simple way to adjust your Python path within your Heroku environment (and not mucking with your app) is by setting the PYTHONPATH env variable as follows:

$ heroku config:add PYTHONPATH=/app

To confirm it is set correctly, run:

$ heroku run env | grep PYTHONPATH
PYTHONPATH=/app

Now you can run your custom management command. This also allows you to run these as cron (scheduled) tasks:

$ heroku run python your_app/manage.py your_custom_command
Success!

I hope this post will save you some headbanging. Unless, of course, it’s to the Melvins.