It’s been a while since I last posted here. I’ve been pretty busy with work, I’ve also moved to Cracov in the meantime - so yeah, I can’t really complaint about boredom ;).

Anyhow, some time ago I posted about Mina - neat little gem that allows super fast deployments. Unfortunately when your project grows you start adding additional bash scripts, you add more and more commands to your deploy script and it grows into something not-so-neat anymore.

That’s why tempted with Capistrano’s ready-to-use recipes I decided it’s time to say hello to Capistrano. I’ll provide here step-by-step migration process from Mina. I will be using here delayed_job, unicorn (with capistrano-unicorn gem) and thinking-sphinx.

First, install Capistrano and capify your project with command:

capify .

(I assume you need only one deployment environment) Go to config/deploy.rb and add some recipes that are provided by thinking-sphinx, delayed_job and capistrano-unicorn:

require "delayed/recipes"
require "thinking_sphinx/capistrano"
require "capistrano-unicorn"

When you run cap -T in your console you will get the list of available commands that should include newly added toys, isn’t that nice? As always I recommend reading provided manual(s) before proceeding further (especially if you’re using thinking-sphinx, a lot of stuff is happening after simply requiring its recipe).

Next setup your application repository:

set :application, "app name"
set :repository,  "repo path"
set :scm, :git # or whatever you use, this can be guessed by Capistrano if not provided

Now we will setup a destination machine. I’m running my app on shared host and I need to connect to my destination (web) server through shell server first. But in my case that is not enough - I’ll need also provide separate ssh key and set proper PATH and RUBY_VER values so it all works nicely.

server 'destination server', :app, :web, :db, :primary => true
set :gateway, 'shell server'
set :deploy_to, 'my deploy path'
set :user, 'my login'
set :use_sudo, false
set :default_environment, {
  'PATH'     => 'path dependent values',
  'RUBY_VER' => '1.9' # This is required by my shared host provider
}
set :rake, 'bundle exec rake' # and I need to run everything through bundler,
                              # because I'm installing gem to my home directory

ssh_options[:keys] = %w(~/.ssh/my_private_key ~/.ssh/my_web_server_key) # Capistrano will try each key
                                                                        # and I need a different one when
                                                                        # connecting to destination server
                                                                        # through shell server

Now let’s set some Rails env variables:

set :default_env, 'production'
set :rails_env, ENV['RAILS_ENV'] || default_env

I liked that Mina provided shared_paths variable and just linked everything there as you would normally expect. Unfortunately in Capistrano it works a little bit different. You can override shared_children (with is set default to %w(public/system log tmp/pids)), so it would look like this:

set :shared_content, %w(config/database.yml config/something.yml)
set :shared_children, shared_children + shared_content

But what I really don’t like about this solution is that you have to keep your shared/ directory structure flat - I would normally expect to put database.yml in shared/config/database.yml, not inside shared/database.yml, same goes for assets and everything else. So instead I decided to set shared_children to an empty array and create custom task that link all my files and compile assets in a way that I’m used to.

set :shared_children, []
set :shared_content, ['public/system', 'public/assets', 'tmp'] # and it goes on

namespace :deploy do
  task :precompile_assets do
    run "cd -- #{latest_release} && #{rake} RAILS_ENV=#{rails_env.to_s.shellescape} assets:precompile"
  end

  task :symlink_directories do
    shared_content.each do |file|
      run "ln -nfs #{deploy_to}/#{shared_dir}/#{file} #{release_path}/#{file}" # you can pack it into once huge command if you want - execution will be much faster
    end
  end
end

after 'deploy:update_code', 'deploy:symlink_directories', 'deploy:precompile_assets'

Now - I like it much better. What’s let to to is to setup some tasks for delayed_job and for restarting our unicorns:

after 'deploy:restart',     'unicorn:duplicate'
after 'deploy:stop',        'delayed_job:stop'
after 'deploy:start',       'delayed_job:start'
after 'deploy:restart',     'delayed_job:restart'

Why unicorn:duplicate you ask? If you are using unicorn configuration provided by this sample file after sending USR2 signal to master process unicorn should restart itself smoothly and you will get zero downtime deployment.

That’s more or less it when it comes to basic configuration, happy deploying!