Hosting Multiple Websites with the Same Django Project and Processes

I recently completed a website for an organization, and they wanted two more with very similar functionality but different branding. I thought it might make sense to fork the initial repository and maintain and host them as different sites, but this duplicated all the code and required more memory (about 100MB each) and maintenance. I also considered refactoring the common functionality out into a reusable app, but this would require a fair amount of work, and still require more memory and operations maintenance.

As such, I looked into a solution that could meet the following requirements:

  • different domains end up at different Django sub-app’s URLs
  • a single codebase to maintain
  • served by the same gunicorn workers, requiring less memory and processes to maintain

I came across some good solutions, but they involve having separate settings files for each project and having a set of gunicorn processes for each. If that’s what you want, those solutions would be great.

In my case, I just wanted to create a few django sub-applications and have different domains go to the appropriate URLs. Here’s the approach I took.

1. Create a sub-application for each branded version.

This is as simple as running “manage.py startapp”, and creating the URLs, views, models, and such that you want for each app.

For templates, I created a base template for each application that override appropriate branding blocks from a project-wide template. Each individual application template then extended its own branded base template.

2. Include the application URLs in your root URL conf.

I decided to keep each application in its own prefix, as such, in my root urls.py:

url(r'^foo/', include('apps.foo.urls')),
url(r'^bar/', include('apps.bar.urls')),

Now, I could visit example.com/, example.com/foo/, and example.com/bar/ and see the appropriate branded versions with their own templates, views, and models. Almost there.

3. Handle the root URL and error pages for each app.

I didn’t want the applications to be hosted on the same domain or require a specific path to work; I wanted example.com to render the root index and foo.com to show what example.com/foo shows, and so on. I also wanted branded error pages. Here’s the relevant snippet of the root urls.py:

handler404 = "root.views.handler404"
urlpatterns = patterns('',
  url(r'^$', 'root.views.index_meta'),
  ...

So, we define two views in the “root” app: an “index_meta” and a “handler404″. Here’s what those look like:

def index_meta(request):
  from apps.foo.views import index as foo_index
  from apps.bar.views import index as bar_index
  return {"foo.com": foo_index, "bar.com": bar_index}.get(request.get_host(), index)(request)
def handler404(request):
  template = {"foo.com": "foo/404.jinja", "bar.com": "bar/404.jinja"}.get(request.get_host(), "404.jinja")
  rendered = render_to_string(template, {}, RequestContext(request))
  return HttpResponse(rendered, status=404)

These two functions simply look up the correct view / template to render based on request.get_host(), falling back on the root project. My handler404 looks a little different because I’m using Jinja2, but you can get the idea. You could also implement handlers for other errors as you need.

Conclusion

If you’ve got different domains that you want to be hosted by the same codebase, database, and workers, this approach is straightforward and only requires a few lines of special code. On the other hand, if each application needs its own settings.py, database, or differs significantly in functionality, it makes sense to run multiple gunicorn masters pointing at different settings files, or even to create separate projects sharing a reusable app.