Heroku Connect is written primarily in Python using Django. It's an add-on and a platform app, meaning it's built on the Heroku platform. Part of our interface provides users with a realtime dashboard, so we decided to take advantage of socket.io and node.js for websocket communication. But like all Heroku apps, only one type of dyno can serve traffic. This left us with two choices: manage 2 apps, each with its own repo, and carefully consider when and how we deployed them, or find a way to serve both node and Django traffic from the same app.
Luckily, fellow Herokai Dan Peterson had already created a buildpack specifically for running mutiple processes in the same dyno, managed by runit. Using its "sub-Procfile" format, we were able to run node and Django in the same dyno.
Of course, still only 1 process on the web dyno can bind to
PORT. By adding an environment variable (we called it
DJANGO_PORT) and adding node-http-proxy to our node script, we were able to bind node to
PORT and proxy all normal web traffic through to Django over
127.0.0.1. The resulting Procfiles look something like this:
django: gunicorn path.to.wsgi:application --bind 127.0.0.1:$DJANGO_PORT node: node server.js
This solution comes with its own set of challenges. Web dynos now face greater resource contention than either a Django or node dyno would on its own. All processes on a dyno share memory, CPU, and a single filesystem, so choose your dyno type with care. Since
bin/runsvdir-dyno exists only in the buildpack and not in our repo, we "flattened out" our Procfiles into a single file we called
Procfile.local in order to continue to use Foreman for local development. Logs on the web dynos become slightly less useful as
web.1 no longer provides the context needed to map a log line to its source process. This is easily corrected by prefixing loggers in Django and node, but it something to consider.
We've been running this architecture in production without issue for a few months now. The simplicity of a single
git push deploy is a big win, and the overhead involved in proxying Django requests is minimal compared even to the speed of a Django "hello world". The change is completely transparent to our users, but it gives us the building blocks we need to easily add websocket communication to our Django Heroku app.