Using rbenv in Production
I just switched to using rbenv in production so I could easily upgrade to Ruby 1.9.2-p290. 37signals uses rbenv in production like this:
Each app is isolated through disciplined use of RubyGems/Bundler-style binstubs. We start with the stubs Bundler generates (
bundle install --binstubs) and change the shebang from
usr/bin/env ruby-local-exec. Update your Capistrano recipes, Chef cookbooks, Bluepill config, etc to use these binstubs throughout.
This draws a solid line between your app and your ops, meeting at just a handful of binstubs.
/u/apps/basecamp/current/bin/unicornuses the Ruby in
/u/apps/basecamp/current/.rbenv-versionand the Unicorn bundled in
/u/apps/basecamp/current/Gemfile.lock. Your app is self-contained. Your ops tools no longer know or care whether you use Bundler or whether you’re on 1.8.7 or 1.9.3. Achievement unlocked.
Shebangs and binstubs
As far as I can tell, binstubs have two advantages:
- Nothing else need know you use Bundler.
- You don’t need to change into the app directory before running a command.
However I couldn’t get the Rails runner to work via a binstub, and I don’t mind if my “ops tools” know about Bundler. So I decided to forgo the binstubs and stick with
bundle exec. Since I’m using the same Ruby everywhere on the server, via
rbenv global 1.9.2-p290, it’s fine.
But I did need to update my app’s scripts' shebangs to
ruby-local-exec; for example
#!/usr/bin/env ruby-local-exec # This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application. APP_PATH = File.expand_path('../../config/application', __FILE__) require File.expand_path('../../config/boot', __FILE__) require 'rails/commands'
Don’t forget to re-install Passenger in your rbenv-managed Ruby, and
rbenv rehash afterwards.
And on Apache, you’ll also need to update the Passenger configuration to use the new Ruby.
You’ll also need to install (and
rbenv rehash) non-Gemfile gems, such as those your crontab commands use. For example I use a gem via cron to backup the database to S3.
With rbenv managing Ruby, and Bundler managing my app’s gems, everything is nicely isolated. As Jeremey Kemper went on to say:
Now, say Ruby 1.9.3-rc1 just came out and you want to upgrade your app. Push the new package to your servers (using Chef or whatever), change
.rbenv-versionfrom 1.9.3-preview1 to 1.9.3-rc1, and
cap deploy. That’s it.
Didn’t work out?
cap deploy:rollbackand you’re back at 1.9.3-preview1. Ahh.
Which is lucky, because my Rails 3.0.10 app takes a glacial 3m47s to start up on Ruby 1.9.2-p290. As my friend Kamyar says, that’s not a computer number: ~4 minutes on modern CPUs corresponds to gazillions of instructions and we are not modelling the climate here.
So I’m going to take advantage of my easily-upgradable environment and upgrade to 1.9.3-rc1 as soon as possible.