Omnibus Tutorial: Package a standalone Ruby gem

A couple of years ago I visited Argentina. I have trouble enough pronouncing my limited English vocabulary and I don't speak Spanish, but after a bit of time, it was pretty easy to order food, buy groceries, and use a taxi. However, occasional hangups that happen during my regular life in the states would throw me out of sorts in Spanish: a taxi driver trying to explain he doesn't have enough change would send me off the rails.

Ruby is my English when it comes to writing software, so when I hit hangups installing something Ruby-related, I can usually work my way out of them. Our monitoring agent at Scout is a Ruby gem, and while most of our customers already have Ruby installed, for those that don't a seemingly small hangup to me can be frustrating for them.

Now, thanks to Omnibus, there's an easy way to distribute your Ruby gems as standalone, full-stack program. This means folks without Ruby can have as smooth of an experience with your hip new gem as a hardened Rubyist.

Meet Omnibus

Omnibus is a Ruby gem for creating full-stack installers - it's everything your customers have to install and configure to get your software to run. It's Chef for packages, and like Chef, it's built by the folks at Opscode. Omnibus lets you build full-stack installers that won't conflict with existing versions of software.

With Omnibus, you define a project, software, and its dependencies. Then, via Vagrant, you build the project on the VMs of your choice (Omnibus includes four flavors of Linux by default). Once the builds complete, you have platform-specific, full-stack versions of your software.

The shell script detects your platform, fetches the appropriate package, and installs Chef and all of its dependencies (like Ruby). Magic?

Lets build an embedded Ruby gem executable

I'm going to step through the process of building a package for a Ruby gem, Scout, with Omnibus. With this package, folks won't need to have Ruby installed to use Scout - the package will contain its own embedded Ruby.

Omnibus requires Ruby 1.9+.

Setup

Install the omnibus gem:

$ gem install omnibus

Create an Omnibus project:

$ omnibus project scout
$ cd omnibus-scout
$ bundle install

This creates an Omnibus project in the omnibus-scout directory. There are two primary configuration pieces, the project DSL (config/projects/scout.rb) and software DSLs (config/software/*.rb). The created directory is where we'll perform all of our omnibus commands (similar to how you'd perform all of your Rails-related commands inside a Rails project directory).

While a project may have multiple project DSLs, you'll just have one (I'm not sure of the use-case for multiple project DSLs - perhaps slightly different flavors of the same package?). However, its likely an Omnibus project will have several software DSLs. For example, if you were building a full-stack Ruby on Rails application, your single Rails project might contain several pieces pieces of software:

After running bundle install, you'll have many sofware DSLs available to you via the omnibus-software gem. From Ruby to Nginx to PostgreSQL, omnibus-software contains a number of software DSLs that are likely to be a part of your full-stack package.

Lets start with the software DSL.

A software DSL for our Ruby gem

I'll start by creating a config/software/scout.rb DSL. Scout is a Ruby gem, so its full-stack is:

Build Prep

Building our package

Now for the magic! Lets build our package:

$ omnibus build project scout

This will build the package on your development environment. Since I develop on OSX, this will build a package for OSX. Note that building a package takes a while - it took me about 25 minutes on my MacBook Air.

The package will be placed in pkg/scout-*. Before installing, I'll clear out the /opt/scout directory that Omnibus populated during the build:

Now you have a full-stack Scout, completely isolated from any existing software. It has its own Ruby, Rubygems, and Scout gem that is separate from the versions already installed on my machine.

Building Packages for other platforms

There's a good chance you'll want to build packages for other platforms. You can do that! When you ran omnibus build project scout above, you built the package for your OS. Omnibus uses Vagrant to fire up VM images of other platforms and runs omnibus build project scout on each, dumping the packages to the project's pkg/ directory.

Out-of-the-box, Omnibus supports a number of platforms:

$ vagrant status
Current machine states:
ubuntu-10.04 not created (virtualbox)
ubuntu-11.04 not created (virtualbox)
ubuntu-12.04 not created (virtualbox)
centos-5 not created (virtualbox)
centos-6 not created (virtualbox)

You're free to modify the Vagrant file to add or remove platforms.

Lets say you want to build a scout package for Ubuntu 10.04:

$ vagrant up ubuntu-10.04

This creates a Debian package, placing it in:

pkg/scout_*.ubuntu.10.04_amd64.deb

I've put this package on Dropbox, so you could install it on your Ubuntu 10 server:

Again, just like building the package on your development machine, building a package on a VM takes a while.

As a Ruby developer, should I be using Omnibus to package my Ruby gems?

It likely doesn't make sense to package your Ruby gems in most cases. Our motivations:

We have customers that haven't worked much with Ruby and the Ruby install was intimidating.

The wider adoption of tools like Bundler and RVM have complicated the process of running our scout executable via Cron. A self-contained Scout can be a simpler option.

Our customers that have Ruby might not use it on all of their servers (for example, their database servers may not need Ruby).

Awkward Nerd Fist Bump

I experimented with Omnibus a bit before it reached 1.0 and didn't have much luck. An awkward nerd high-five to Tim Ray of Rackspace for building an initial version of omnibus-scout to get us started.

TL;DR

Omnibus lets you build full-stack installers for your software. It makes it easy for folks to install our software by avoiding dependency hell. Because it's fully-contained, it won't conflict with the existing pieces of your system. It's a great option for software that has a number of dependencies and/or isn't managed through an existing package management system.