At Flowdock, we have currently four different Node.js apps. Worst case scenario: they all could require a different Node version. This blog post explains how to use chef and capistrano to deploy multiple Node.js versions.

TL;DR: Check out our deployment resources and enjoy multiple Node.js version bliss.

Chef Recipe for Node

Chef is used at Flowdock to configure servers. Naturally we wanted to use it also for managing our Node versions.

We ended up with having a recipe for compiling Node packages and uploading them to Amazon S3 and another recipe to download the correct binaries. If you want to see all the gory details, we’ve published our Node.js recipes.

As an end result, we can have a machine with all necessary Node versions set up in practically no time and applications can choose Node versions with little $PATH mangling.

Capistrano scripts for Node

Capistrano has proven itself and, above all, there’s an awesome Flowdock integration for it. There probably are Node-based alternatives, but we didn’t feel bold enough to try them out.

To avoid any hubbub during deploy, each deploy should use the Node version defined in package.json. This allows us to try out new version of Node without any changes in system configuration. Also, if the deployment is bad, we can always roll back to a working version.

To achieve this, our Capistrano script reads the package.json definition, extracts Node version constraints and uses Gem::Requirement from rubygems to choose satisfying version candidate. During deployment node and npm binaries are symlinked to $PROJECT/bin.

Keeping Node up and running

Now that the basic environment is set up, we just need something to manage our Node services. Our boxes already have runit for this task, so why not use it for our Node apps, too? You can even use foreman to export the service definitions for you.

Here’s how a runit definition looks like for our Streaming API.

#!/bin/sh
cd /home/florence/florence/current
exec chpst -u florence -e /home/florence/env sh script/run

This uses envdir to configure environment variables. script/run is responsible for mangling the $PATH and other fine-tuning.

#!/bin/sh
exec &2>1
export PATH=`pwd`/bin:$PATH
./node_modules/.bin/coffee app.coffee

Capistrano tasks are almost trivial.

task :start, :roles => :app, :except => { :no_release => true } do
  run "sudo sv up florence"
end

task :stop, :roles => :app, :except => { :no_release => true } do
run "sudo sv down florence"
end

task :restart, :roles => :app, :except => { :no_release => true } do
  run "sudo sv term florence"
end

Of course, you can also use upstart or supervisord if that suits you better.

Added bonus – Continous Integration

I’m lazy and haven’t managed to set up a setup for multiple Node versions on my personal development machine. To ensure that my changes will work when deployed, CI needs to use the same version of Node as in deployment.

To solve this, our Capistrano script has a local mode, which does the same symlinking stuff, but to a local directory. The CI script adds ./bin to $PATH before starting the test suite.

Conclusion

Having flexible Node version management on deployment and CI has been a blast. We can even try out unstable versions without extra ops work.