Serving Django Static Files on Heroku (Posted on December 15th, 2012)

One of my goals when creating this blog was to create a blog that I never had to pay for. Luckily Heroku's free tier allowed me to do just that. While my blog doesn't have many static files (CSS, JS and images) Heroku typically recommends that you host them on Amazon which isn't free. However, Heroku gives you 100mb of free space for your app so I wanted to take full advantage of that and the free bandwidth they offer.

I tested a few different webservers and found Tornado to work the best after some load testing with Blitz.io. The other ones I tested were CherryPy, FAPWS3, and Gunicorn with Tornado. After a bit of tinkering with getting Tornado to host static files here is what I came up with (go ahead and replace "burstolio" with the name of your app):

import os, sys
import django.core.handlers.wsgi
from tornado import httpserver, ioloop, wsgi, web

from optparse import OptionParser
parser = OptionParser()

parser.add_option('--port', dest='port')

options, args = parser.parse_args()
        
def runserver():
    app_dir = os.path.abspath(os.path.dirname(__file__))
    sys.path.append(os.path.dirname(app_dir))
    os.environ['DJANGO_SETTINGS_MODULE'] = 'burstolio.settings'
    wsgi_app = wsgi.WSGIContainer(django.core.handlers.wsgi.WSGIHandler())
    application = web.Application([
        (r"/static/(.*)", web.StaticFileHandler, {"path": "/app/burstolio/static"}),
        (r".*", web.FallbackHandler, dict(fallback=wsgi_app)),
    ])

    server = httpserver.HTTPServer(application)
    server.listen(options.port)
    try:
        ioloop.IOLoop.instance().start()
    except KeyboardInterrupt:
        sys.exit(0)

if __name__ == '__main__':
    runserver()

The first part allows me to specify a port which Heroku will assign automatically. Getting into the runserver function we first start by adding the app to the system path so that our app can be found. After that we create a WSGI (Web Server Gateway Interface) container to hold our app in a way that we can serve to users.

The next part is the important part where the web app's URLs are setup. It's important that the static files go first otherwise Tornado will match the WSGI container regex which will 404. Make sure that your settings.py file has the STATIC_URL and STATIC_ROOT set to match the regex you specify for the StaticFileHandler. Here is an example of how I setup my settings.py. Once the web app is setup the rest is as simple as creating an HTTP server and listening on the specified port.

If you're running on Heroku and want to give this a try your procfile should look something like:

web: python tornadows.py --port=$PORT

If you know of a better way to host static files on heroku, run into any problems or have any questions let me know in the comments!

Tags: Django, Heroku