Capistrano, system wide RVM and creating gemsets as part of deploy:setup
August 05, 2011
Introduction to the Problem
Capistrano is the standard way to deploy Rails apps. Yesterday I was using Capistrano to deploy to a machine where I had installed RVM (Ruby Version Manager) at the system level.
I manually set up Ruby 1.8.7 and Ruby 1.9.2, because I need to run two applications on that machine (one a Ruby 1.8.7 app and one a Ruby 1.9.2 app). Using RVM for production deploys is great for this.
My cap deploy:setup
task, however, complained that Ruby 1.9.2 wasn’t installed on the machine.
That’s funny, because I did install it, I thought. After banging my head up against the problem for a few hours, I finally posted the question to Stackoverflow.com: (Capistrano deploying to system wide RVM not seeing installed Rubies)
I got my answer: the message about the Ruby not being installed was misleading, it actually meant that the gemset wasn’t installed. Which it wasn’t (I was planning on doing that as part of the cap deploy:setup
task.
Creating gemsets in your deploy:setup
step
Ideally I want cap delpoy:setup
to take care of eveything for me: installing some rubies, creating the appropriate gemsets, you name it. Because automated deployments mean everything should be automated (amirite?).
But then I get errors like this when I’m trying to create the gemset I want to use!
It’s non-obvious how to do this - and in fact the obvious way will not work!
Background
You see, require 'rvm/capistrano'
hooks into the low levels of Capistrano’s run
function, meaning everything happens in the context of the ruby+gemset that you declared in your Capfile. (Technically rvm/capistrano
uses a user shell called rvm-shell
, instead of bash
or sh
. This shell knows enough to properly set your paths to Ruby etc etc.
Normally this is awesome - that means that Capistrano knows about your Gemset, and installs gems there etc etc. Capistrano’s run
command just does the right thing.
However, there are two cases where you want things to happen outside of rvm-shell
:
- Installing the Ruby
- Creating the Gemset
If you try to do these things using run
, Capistrano will give an error about Ruby not being installed, like it gave me. Even if RVM is trying to say, “I don’t see that gemset”, the error message will be about a missing Ruby.
The obvious solution, and why it doesn’t work (as a conversation)
The obvious thing you might try in your Capfile is this command:
run "rvm install 1.9.2"
Except, as I explained above, that won’t work. Here’s what’s going on, as a conversation.
You, to Capistrano: Run this command for me
Capistrano, to remote machine: Hey, I want to log into this machine, using the rvm-shell
command, using Ruby 1.9.2 and gemset MY_APP. When I’m logged in please execute rvm install 1.9.2
Remote machine, to Capistrano: Could not log you in, an error happened when firing up rvm-shell
. I could not find the ruby/gemset you wanted, so I can’t set the Ruby paths appropriately. I’m giving up and stopping because I can’t possibly do whatever that command was that you wanted me to execute
Capistrano, to you: I couldn’t install that Ruby you wanted me to install because I can’t activate that Ruby you want me to use for the gemset you want me to use - I don’t think that Ruby is installed!
You: Le sigh.
The solution: avoid rvm-shell
for Ruby installation AND gemset creation
You might think that you need to avoid rvm-shell
just for the installation of your Ruby. In fact, you need to avoid rvm-shell
for both the installation of Ruby and the creation of your gemset!
How to avoid rvm-shell
Define this method in your Capfile.
`def disable_rvm_shell(&block) old_shell = self[:default_shell] self[:default_shell] = nil yield self[:default_shell] = old_shell end
`
Now, in the context of that block, run
will execute commands by using sh/bash as a shell, instead of rvm-shell
.
In your Capfile, install Ruby and your gemsets by:
disable_rvm_shell do run "rvm install 1.9.2" run "rvm use 1.9.2@MY_APP --create" end
This installation process must come before any other command in your deploy:setup
chain.
Conclusion
And that’s that - I really hope this helps someone out there!