Service Oriented Foreman

November 9, 2012 Matthew Kocher

I’m a big fan of David Dollar’s foreman for starting up everything you need to run an app. These days were seeing a number of projects that rely on multiple ruby apps with seperate gemfiles to have a usuable development environment.

On one project recently we had three external services which we did not want to depend on for development. We wrote a tiny rails app for each, which had json endpoints, models for fake data, and used Rails Admin almost exclusively for the UI. This was great, but starting each one by hand was a chore, and foreman didn’t want to respect the individual gemfiles.

The first thing that makes this dificult is bundler, which attempts to be helpful and make sure your gemset is still in effect when you shell out. How it does this is an ugly or beautiful hack, depending on your point of view.

    1.9.3p194 :001 > ENV['RUBYOPT']
    => nil
    1.9.3p194 :002 > require 'bundler'
    => true
    1.9.3p194 :003 > Bundler.require
    => [...]
    1.9.3p194 :004 > ENV['RUBYOPT']
    => "-I/Users/mkocher/.rvm/gems/ruby-1.9.3-p194@soloist/gems/bundler-1.2.0.rc.2/lib -rbundler/setup"

As you can see, Bundler has set RUBYOPT to load itself every time you shell out. We can prevent this from happening by using Bundler.with_clean_env.

    1.9.3p194 :005 > Bundler.with_clean_env { ENV['RUBYOPT'] }
    => nil

Better! But we’re not out of the woods yet.

RVM has recently started including rubygems-bundler in the global gemset. I with they hadn’t, but I also don’t want to tell people to uninstall it every time they get a new workstation.

To prevent rubygems-bundler from trying to keep you in your gemset, you’ll want to add a NOEXEC=skip to your environment.

With ruby, the cleanest way to do this is to include the env hash in your call to system:

  system({'NOEXEC'=>'skip'}, "rake -T")

Putting this all together, we made a script called run_app which looks like

    #!/usr/bin/env ruby
    require 'bundler'

    cmd = %Q{bash -lc "cd ../#{ARGV[0]} && source #{ENV['rvm_path']}/scripts/rvm && exec rackup -p #{ARGV[1]}"}

    Bundler.with_clean_env do
      system({'NOEXEC'=>'skip'}, cmd)

We call this from our main project, and can pass it a directory and the port we want it to run on. foreman start now gives us an entire development environment nicely logging to one terminal.

About the Author


More Content by Matthew Kocher
Managing Hot and Cold Data Using a Unified Storage System
Managing Hot and Cold Data Using a Unified Storage System

Greenplum is working on a project that abstracts various storage options within an organization under a uni...

Dealing with issues in third-party libraries
Dealing with issues in third-party libraries

Sometimes, despite your best efforts and despite following the documentation, the third-party library you c...

Enter curious. Exit smarter.

Register Now