Vlad, RVM and Bundler sittin’ in a tree

Vlad the Deployer(Thanks to Chad for nudging me to write this post :)

Up until now, my only experience with deploying Rails or any other Ruby-based web application
has been to use the “standard” Capistrano.  For the main Rails 3 application I’ve been building
the past few months, Capistrano has been fine and was mostly a set it and forget it kinda thing.

One of my clients asked me to do a small side project to perform a simple task and naturally I chose
the most excellent Sinatra framework. I could go on and on about the awesomeness of Sinatra, but that’s
not the point of this post. Suffice it to say when I finished this small Sinatra application, I needed to
deploy it somehow. Manual deployments are for the birds, so my first knee-jerk reaction was to get
Capistrano wired up to do the automated deployment. But after taking a glance at my existing Capistrano
deployment script for my Rails 3 application, it seemed like overkill for this tiny Sinatra ditty.

The first alternative I ran across was a deployment tool named Vlad the Deployer
(which clearly gets an award for the best name ever). What attracted me right off the bat was the simplicity of its usage
and configuration. I also liked the fact that it uses pure Rake for everything, instead of some of the
“magic” that Capristrano does under the hood.

Getting Vlad up and running is pretty straightforward, but I did have a couple issues getting it integrated
into my particular deployment process. Like all good Ruby developers 2 of my must use tools are RVM
& Bundler.  Bundler is great for managing gem dependencies and RVM keeps you sane when working with many different versions
of Ruby and gems at the same time.

One thing I like to do during deployments is make sure “bundle install” gets run on the server.  This ensures that any new gems I’ve introduced in my Gemfile get automatically installed on the server
during deployment. Usually this wouldn’t be much of an issue, but I’m a pretty big fan of RVM, so much
so that I actually run it on my staging/production servers as well. RVM does some serious magic under
the hood, including altering various environment variables affecting the path. Here is the series of
steps I had to take to get Vlad to properly run “bundle install” during deployment. (NOTE: I’m mainly
posting this to hopefully get feedback on a better way. I really don’t like my solution too much.)

Minor server preparation

Before I show the deploy script itself, you do need to make sure your server is set up properly with
RVM and Bundler installed in the global gemset. Here are 2 great articles that describe this process:

 

Extend Vlad’s update Rake task

Since Vlad uses simple Rake tasks for everything, it’s easy to “tack on” steps before or after the
built in Vlad tasks. In this case I wanted to run my Bundler command right after the built in update
process was complete. Here’s one easy way to do that:

FYI, if you want to run something before the update process starts, you can simply add a dependency
to the built in update task like so:

Create a Bundler task

Next I created a separate task (see next section about remote_task) to perform the Bundler command I
needed to run on the server and invoked it inside of Vlad’s built in update task:

Remotely running commands via SSH

Before I show the commands necessary, it’s important to understand that all of this will be run in
the context of an SSH session. Thankfully, there was a nice feature of Vlad that was extracted out
into its own gem known as remote_task.  This is a handy way to run Rake tasks in the context of remote
servers and is used heavily under the hood with Vlad. We’re also using it here for our custom Bundler
task and a “run” method can be called with whatever commands you want to be run on the remote server in
an SSH session.

Step by step

For clarity I put each command into its own local variable, each of which I’ll describe below.

Initialize RVM

When you login to a server via SSH, you have set of environment variables which include how paths
are resolved when running commands. Luckily, RVM takes care of all of that for us. Usually when using
RVM you simply load it via your .bashrc, but for some reason I couldn’t get this working in the
context of the SSH session used as part of the remote_task. I’m sure this is due to my lack of bash and
*nix skills which I’m actively trying to beef up. But to work around it for now, I just manually source
the RVM bootstrap script myself:

Trust your RVM gemset

I’m not going to dive into RVM gemsets as part of this post, but just think of it as a way to manage gems
in isolation from other applications and environments. I like to use project-specific gemsets for everything
I do to keep things nice and clean. A nice companion to gemsets is the use of an .rvmrc file to
automatically switch to the correct gemset when navigating to your application’s directory. Creating a
.rvmrc is stupidly simple:

Starting in version 1.0 of RVM,
there was a security measure put in place to force you to “trust” .rvmrc
files when changing into a directory with a .rvmrc for the first time. Normally this is fine, but it
presented an issue in the context of an automated script. This security measure can be disabled by this
next command:

This tells RVM that I explicity trust the .rvmrc located in my release_path which is the root of my
application on the server.

Run bundle install – take 1

With RVM all loaded up, we can now issue our bundle command to install any new dependencies if necessary.
So naturally I tried the command below:

But this blew up in my face with a nasty exception:

Run bundle install – take 2

I had read somewhere previously (sorry, can’t remember exactly where) about sometimes needing to
explicitly specify the target path for the “bundle install” command. In this case I can just use
the $BUNDLE_PATH environment variable that RVM manages for me:

This seems to fix the exception above, but I’ll be honest, I’m not exactly sure why yet. (And yes,
that does bug the heck out of me)

Putting it all together

Now that we have all of our commands ready to go, we can simply call the built in “run” method
and pass in each command concatenated one after another:

If all is well, you should see a nice green message from Bundler saying your bundle is complete.

</post>

As I mentioned before I’m not all that happy with this solution, as it seems like there is probably
a better way to get Vlad, RVM and Bundler all working nicely together. I’d be really interested
to know of a better way.

Anyways, I hope this post benefits somebody in the future. Even if it’s myself a year from now. :)

Related Articles:

    Post Footer automatically generated by Add Post Footer Plugin for wordpress.

    This entry was posted in bundler, deployment, ruby, rvm, vlad the deployer. Bookmark the permalink. Follow any comments here with the RSS feed for this post.

    9 Responses to Vlad, RVM and Bundler sittin’ in a tree

    1. Peter Mounce says:

      In the context of running this to automate _Windows_ deployments, is there a way to replace the run-via-ssh part with, say, a run-via-powershell-remoting, or run-via-webdeploy-remote-execution?

    2. @Peter,
      I’m afraid I don’t have a clue about running this on Windows. But I do know that under the hood it uses POpen4, which looks to be a cross-platform tool, so remote_task could maybe be modified to take advantage powershell, etc. Can’t say for sure though since I don’t use Windows.

    3. This is great stuff. I’ve been growing increasingly disillusioned with Capistrano lately (probably in direct correlation to the increasing complexity of my deploy scripts) and it’s good to find a simple, elegant solution. I was an overnight convert as RVM goes, and am considering running it in production just as you mentioned, so this will be a helpful resource — especially since I’m also beginning to understand the real power in using Bundler to manage gems.

      Clearly there’s a huge plus (IMHO) to not having to “sudo” everything under the sun, but have you discovered any other major benefits / drawbacks to using RVM in production, or does this come largely down to preference?

      BTW – thanks for the link. It’s good to know someone out there is gaining from my own meager contributions. :)

    4. Joey,

      Regarding the .rvmrc file, the best way to generate this is to run:

      $ rvm –rvmrc –create ree@ema

      In the project root directory. This is optimized to load the environment file if it exists which is *much* faster than running through the entire RVM CLI parser.

      Additionally each .rvmrc file is simply a shell script. This means that you can, for example, that you can run your bundle install on your server. Something like this:

      … original .rvmrc generated content …

      if [[ "$(hostname)" = "my-server-name" ]] ; then
      bundle install
      fi

      Of course you can make this as elaborate and/or tailored to your environment as you need!

      ~Wayne

    5. @Colin,
      Cool man, glad you liked it. I gotta say RVM has definitely spoiled me, which is why I couldn’t stand NOT having it on my servers as well. I’ve always been a huge proponent of portability when it comes to my apps and development environments, so RVM is just a no brainer for me.

      And yeah, your article on I linked to is probably one of the best I’ve seen on that topic, so thank you! :)

    6. @Wayne,
      Very cool stuff man, thanks! I knew the .rvmrc file was just a shell script but didn’t know about that nifty command to generate one for you. Very handy!

    7. Ben says:

      Thanks for showing the trust command!

      I found a nice way to join and run the commands: http://pastie.org/1169652

    8. Ben says:

      Also having the bundle install location is very handy

    9. Vidar says:

      On my machine I’m not using gemsets but I’m using vlad, rvm and rails3. All I did when I upgraded from my rails2 app was to add

      remote_task :bundle do
      run “cd #{release_path} && bundle install”
      end

      to my deploy.rb and add the PATH in .ssh/environment like this:

      PATH=/home/vidar/.rvm/gems/ree-1.8.7-2010.02/bin:/home/vidar/.rvm/gems/ree-1.8.7-2010.02@global/bin:/home/vidar/.rvm/rubies/ree-1.8.7-2010.02/bin:/home/vidar/.r
      vm/bin:/usr/local/bin:/usr/bin:/bin

      and it just worked.