jhollinger

code et al
jordan at jordanhollinger dot com
powered by eridu

Getting Bundler and Thin to play nicely

November 29, 2011 | ruby, thin

Before I get your hopes up, let me clearly state that there is no hope. Bundler and system-installed Thin will never play well together. The only bright spot here is that, since the universe is ultimately doomed to self-destruction, none of this matters, cosmically speaking.

You've probably seen a message like this when starting a Rack/Rails app on Thin

You have already activated rack 1.3.0, but your Gemfile requires rack 1.2.3.
Consider using bundle exec. (Gem::LoadError)

What that means is Thin (which uses Rack) loaded Rack 1.3.0 when it started up. Now Thin is loading your application (which also uses Rack). Your app has bundled Rack 1.2.3, and when Thin tries to load it, Ruby notices the version conflict. Ruby can't handle it and promptly explodes.

The problem

You probably tried to start your app with /etc/init.d/thin start. This means that Thin and its dependencies (e.g. Rack 1.3.0) are installed someplace like /usr/local/ruby/. And doubtlessly you used bundle install to install your apps dependencies (e.g. Rack 1.2.3) to /path/to/app/vendor/bundle. As you can see, you are using gems from two separate install points, which were installed irrespective of each other's dependencies. Sometimes they happen to match up, but that's rare. Yehuda Katz, a respected Rubyist, has this to say on the matter.

The solution's problem

The article is a good one, and a convincing case for bundling tools like rake and cucumber with your app. Some, though, have used it as an argument for bundling Thin with your app to fix the above problems. They say you should use bundle exec thin start -C /etc/thin/app.yml to start your app.

I'm telling you that's a terrible idea, both practically and philosophically. In practice, it means you can't depend on /etc/init.d/thin anymore. That's something I am not willing to give up. It's by far the best way to manage a serious Thin deployment. I suppose you could rewrite it to work somehow, but that brings us to the philosophical discussion.

Bundler is a fine piece of software written by talented people. The problem is that it shouldn't exist. It's a tragedy, really. If Ruby Gems had been designed better, there would be no need for extra and confusing dependency management tools like Bundler. It's a necessary evil, nothing more. As such, it should not unduly influence the rest of your application or deployment strategy. In my book, bundling your app server in with your application "just to make Bundler happy" is undue influence. It's as inappropriate as bundling Nginx or the Linux kernel with your app. People may want to use Passenger, Apache, and OSX instead.

A better solution? Not really, but maybe a less bad one

My favorite solution would make most Bundlerists gag, but I make no apologies. As I said, Bundler is merely a necessary evil, and beyond installing our app's dependencies, it doesn't deserve our attention. Usually, the version of Rack specified in Gemfile.lock is a "soft" dependency - your app will work fine with a wide range of versions. With that in mind, I update my system's Rack to the latest version, then specify that Rack version in all my apps' Gemfile.lock files (testing them first, of course). Then, your system and bundled Rack's will match up, and /etc/init.d/thin start will work just fine.

Yes, that goes against everything in Yehuda's post. But we're talking about a Web server here, not some little utility like rake. You have to make some assumptions about your environment, and Rack is just one of them.

The only other option, which I have not actually explored, is to bundle Thin with all your apps and rewrite /etc/init.d/thin to visit each app and run bundle exec thin start -C .... In my opinion that's a very distant second.

If you've found a better solution, please let me know in the comments!

comments powered by Disqus