Deploying Multiple Heroku Apps From a Single Repo
We’ve been toying around with socket.io recently at Harvest to make our apps more responsive — and one of the big challenges that presented itself was how to manage and deploy a polyglot application on Heroku (as most of our application code is in Ruby, but socket.io is a node.js project).
Since the “web” process is the only process to receive HTTP traffic and only one web process per application is allowed, we needed to deploy the Ruby and node.js parts of the application separately.
Traditionally, there is a one-to-one mapping of repositories to apps on Heroku — but we wanted to keep both apps in the same repository (as they’re really the same thing, conceptually) and yet deploy them as two apps on Heroku. So, what’s standing in the way?
Heroku Buildpacks
Heroku uses this thing called buildpacks — the utilities and frameworks that are required to fetch your app and install dependencies. There are different buildpacks for ruby and node.js on Heroku.
Buildpacks are usually automatically chosen — but since we’re pushing the same code to both apps, the order in which buildpacks are selected matters. Turns out that ruby is preferred over node.js.
No worries, you can select a buildpack manually with an environmental variable on Heroku’s server — which is what we did to the node.js app, and it worked like a charm.
Procfiles and Foreman
The only other thing standing in our way was the way that Heroku starts web workers — using Foreman and a Procfile to define processes.
With ruby applications, the Procfile is optional — it will try to start a Rails application if it isn’t specified.
When deploying node.js to Heroku, you must specify a Procfile — there is no default command to start a server. Since we want to be able to push the same code to both Heroku remotes without any modifications, we got a little clever with the web process in the Procfile — it executes a shell script which examines the environment and runs the proper web process.
#!/bin/bash
if [ "$RAILS_DEPLOYMENT" == "true" ]; then
bundle exec rails server -p $PORT
else
node node/index.js
fi
Procfile:
web: bin/web
Hat tip to David Dollar (the creator of Foreman) for the idea.
Happy polyglotin’!