Bundler and Capistrano – The Right Way

While working on my current project I use Bundler. There are many resources on the web which says how to use it in pair with Capistrano.

Most of them, however, fails when it comes to

  • rollback Capistrano transaction as well as
  • rollback Capistrano task.

Let’s say you used already a solution similar to this.

namespace :bundler do
  task :create_symlink, :roles => :app do
    shared_dir = File.join(shared_path, 'bundle')
    release_dir = File.join(current_release, '.bundle')
    run("mkdir -p #{shared_dir} && ln -s #{shared_dir} #{release_dir}")
  end

  task :bundle_new_release, :roles => :app do
    bundler.create_symlink
    run "cd #{release_path} && bundle install --without test"
  end
end
  1. Rollback Capistrano transaction

Default deployment path includes transaction with update_code and symlink tasks.

task :update do
  transaction do
    update_code
    symlink
  end
end

If you hook bundle_new_release task before these tasks or within the transaction and do not provide bundler_install rollback action you will get into trouble.

If any of the tasks ran after bundle_new_release fail you will end up with bundle required / installed versions mismatch.

Why bother? Maybe update_code and symlink are not the most likely tasks to fail but you have probably already plenty hooks placed before and after them which may fail…

  1. Rollback Capistrano transaction

You can rollback last Capistrano deployment by simply running

cap deploy:rollback

Simple, huh?

Unfortunately, if you made any change to your bundle in just deployed commits it will not be reverted what will make your application fail to restart.

So now it is time for the solution.

namespace :bundler do
  task :create_symlink, :roles => :app do
    shared_dir = File.join(shared_path, 'bundle')
    release_dir = File.join(release_path, '.bundle')
    run("mkdir -p #{shared_dir} && ln -s #{shared_dir} #{release_dir}")
  end

  task :install, :roles => :app do
    run "cd #{release_path} && bundle install"

    on_rollback do
      if previous_release
        run "cd #{previous_release} && bundle install"
      else
        logger.important "no previous release to rollback to, rollback of bundler:install skipped"
      end
    end
  end

  task :bundle_new_release, :roles => :db do
    bundler.create_symlink
    bundler.install
  end
end

after "deploy:rollback:revision", "bundler:install"
after "deploy:update_code", "bundler:bundle_new_release"

Here are some points to notice:

  • it is useful to put bundle_new_release task within the transaction
  • provide the code run on the transaction rollback
  • provide the code run on the capistrano deploy:rollback task

As a hint I can also say that I find it useful to hook my BackgrounDRb restart task into transaction in the default deployment path.

In this way I can be sure that when the transaction is finished the bundle is installed correctly and the application boots up. It is important as I use <a href=http://unicorn.bogomips.org/’>Unicorn and restart it with sending USR2 signal which will make Unicorn to die silently in case of unbootable application…

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s