Announcing install_gems.rb

by Carl Mercier

Over the weekend, I was rethinking the way we should deploy our Merb application (our API) for Defensio. Everything I’ll say here applies to Rails as well.

Managing gems has been painful lately. Like most people, we’re freezing them, which helps tremendously. However, there’s a few problems with this approach:

  • We can’t freeze C extensions because they need to be compiled for the target platform. We develop on OS X and Linux 64, but deploy to Linux i386. As a workaround, we currently freeze but recompile them on every deployment. Definitely not optimal.
  • Our frozen gems directory is getting huge.
  • It’s becoming complicated and quite a mess to manage multiple gem versions for different branches/tags.
  • We never quite know which branch needs which gem.

Of course, we could manually install the gems we need on our servers. But the word “manually” generally leads to “epic fail”.

So I thought, why not install them at deployment time, just before restarting the Mongrels, with Capistrano? That seemed like a pretty good idea! The only problem is that Rubygems will always install a gem when asked to do so, even if it’s already installed.

That’s where install_gems.rb comes in. install_gems.rb takes a file name as an argument. This file contains a simple list of gems along with their version number. A custom installation command can also be specified.

Here’s an example:

# This is an example file. As you can see, it supports comments.
haml 2.0.3
eventmachine 0.12.2
# Notice the custom command specified after the --
termios 0.9.4 -- gem install termios -v 0.9.4 --no-rdoc --no-ri

To install the above gems, you’d simply run
./install_gems.rb input_file

install_gems.rb has a few advantages over vanilla rubygems:

  • It will only install a gem if the specified version is not already installed. That’s obviously much faster than reinstalling everything every time.
  • C extensions will be properly compiled.
  • Using different gem versions in different branches/tags/releases is now very easy. You just have to maintain a list of required gems in your Git repository and make sure Capistrano runs install_gems.rb against this list before restarting the application servers.
  • It forces us to keep an up-to-date list of the gems we need.

Of course, for this to work as expected, you’ll need to require specific gem versions in your application. Your production server will likely have multiple versions of the same gems installed and if you don’t specify which version of the gem you want to require, things might (or will) break.

I released install_gems.rb on GitHub and Daniel Haran has already submitted a patch. Feel free to do so as well, I’m a puller!