Simplifying the build, test, and run cycle with Rock

At Shutterstock we have over 50 sites and services running in production, across thousands of VMs, in a range of languages — mostly Perl, Ruby, Node, and PHP.  Supporting such a variety of languages across projects can be daunting.  Each project has a specific version of a language runtime it targets (e.g., our Accounts Service runs on Node 0.10.x, but our internal Data Visualization site is still on Node 0.8.x).  And every project has its own set of unit tests and dependencies that need to be managed.

Each language has its own tools for solving these problems.  Perl has Perlbrew for managing language versions and Carton/cpanm for managing dependencies; Ruby has rvm and bundler; Node has nvm (among others) and npm; PHP has Composer (finally!).  These are great, but across many projects it’s still a lot to know and keep track of.

Enter RockStack, the brainchild of Silas Sewell, a developer here at Shutterstock.  Rock provides two main components:

  1. Packaged language runtimes for Ruby, Node, PHP, Python, and Perl (.deb’s for Ubuntu, .rpm’s for CentOS, homebrew for OS X)
  2. A command-line tool to manage setting up environments to build and run projects

With the RockStack packages installed, we can check out any project and use Rock to install/build local dependencies (“rock build”), run tests (“rock test”), and then start up the service (“rock run”).  Rather than having each project contain a specific set of installation instructions in the README for a human to follow, we can put all of that in the .rock.yml configuration file for the project, and Rock takes it from there.

Rock supports a variety of languages and runtimes. To use it, just install the packages and run a command to initialize your project. For instance, a Ruby 2.0 project would use:

 rock --runtime=ruby20 init 

This will create standard dependency files (package.json for Node, Gemfile for Ruby, etc) and directories for your project. Rock has sane defaults for building, testing, and running most code. These are easiest to see by browsing the YAML files that define the default behavior for each language. You can always override these defaults in the .rock.yml file. For a Node app using bower and grunt, your .rock.yml might look like this:

runtime: node010
build: |
    {{ parent }}
    bower install
run: node app run 

Rock is a deceptively simple tool that has proved incredibly valuable.  It has enabled us to continue using multiple languages across many projects, letting us focus on productive hacking, not language logistics.