Menu

Published by
Categories: Ruby, Rails, Capistrano

Like any other build process, deploying Rails applications should be a quick and easy task. You should be able to deploy everything to your server with just one command. There are several tools available for this task. One of them is Capistrano, which is probably the oldest and by far the most popular deployment tool in the Rails community. Unfortunately the documentation is still not great and a lot of very handy features are unknown to many. Only digging through the API documentation and the source code reveals them.

Variables

A lot of tasks for deploying Rails applications are the same for almost every application. They only differ by minor aspects such as the path to deploy to, or the branch to checkout the code from. Capistrano allows setting these aspects using variables. Setting a variable is as simple as calling set :name, 'value'. Accessing a previously defined variable is even easier by just using it as a ruby method.

set :name, 'value'
puts name # Prints 'value'

However there are some aspects of Capistrano variables that make them really useful.

Accessing variables that might not exist

In some cases you don’t want to set every single variable to configure certain aspects of a task. But this comes with a problem. Accessing a variable that has not been defined before, will make Capistrano yell at you and crash with a NameError.

There are two ways to handle this. One of them is to define all variables with sane defaults and overwrite them as needed afterwards. The other one, and by far my preferred one, is to use Capistrano’s fetch method. It allows you to access a variable and additionally provide a default that will be returned when the variable is not defined, yet. This allows you to write flexible tasks without having to define every single variable upfront.

fetch(:some_setting, false)

Setting variables via the command line

Another very useful feature is the possibility to set variables via the command line. Appending a -S variable_name=value option to the command line will set the given variable to the given value. This comes with a minor caveat, though. Any call of set with the same variable name will overwrite the value set by the command line option. Luckily Capistrano’s exists? method allows you to check for the existence of a variable. This allows you to only set a variable if it doesn’t exist yet. You might change your set :branch call to something like this:

set(:branch, 'staging') unless exists?(:branch)

This allows you to deploy any branch to your staging servers without fiddling with the configuration files, just by appending -S branch=other-branch to your cap deploy call:

cap deploy -S branch=awesome-new-feature

Combining the ability to set variables via the command line with the previously explained fetch method is also useful to toggle certain aspects of your deployment.

namespace :translations do
  task :update do
    if fetch(:with_translations, true)
      # Download current translations from your translation service provider
    end
  end
end

Reading passwords from STDIN

Variables are great to store your configuration options. There’s just one exception: Passwords. You probably don’t want to have your production database password checked into your repository. This is where Capistrano’s command line interface tools come in handy. It allows you, among several other options, to read passwords from STDIN. It even makes sure your terminal won’t print it while you’re typing.

Capistrano::CLI.password_prompt('Enter database password: ')

Task descriptions

As you probably know, it’s possible to list all available tasks using cap -T. This will print a list of all available commands. Every task with a description will show up in this list. Adding a description is easy. Just prepend the task definition with a desc call, passing it a string.

Adding extended description

Each defined task may also have an extended description. To show it, simply execute cap -e task name. Adding an extended description works just like adding the short description explained above. Just like git commit, Capistrano will use the first paragraph as short description and every following paragraph as extended description. Ruby’s heredoc literal is handy when defining extended descriptions:

desc <<-EOS
  This is a short description that is visible in the `cap -T` list

  This the extended description that is visible when executing `cap -e task name`
EOS

Hiding internal tasks

Some tasks you’ll define for your deployment process are not intended to be executed manually and therefore shouldn’t show up when using cap -T. The simplest way to prevent this, is to not give them any description. However, in some cases, a description would be useful for later reference and documentation. By prepending the description with [internal] you can hide the task from the cap -T list. It will then only be visible when executing cap -vT, which will list absolutely all defined tasks.

desc '[internal] This task does something useful but you should not invoke it manually'

Executing commands

Running commands on the remote servers is easy. Capistrano’s run method will execute the given command on every server the current task applies to. There are two more methods related to command execution that are very useful, though.

Running local commands

One of them is run_locally. It behaves just like run, except it will run the command on your machine. While it is of course possible to use Ruby’s various methods for command execution, run_locally will ensure both the command and it’s output will show up cleanly aligned with all other output of the Capistrano run.

run_locally 'bundle exec rake deploy:prepare'

Streaming output from remote servers

The other very useful method is stream. It will execute the given command on every server the task applies to, but unlike the run method it will stream the command’s output to your console. This allows you, for example, to peek into your log files in realtime. Combined with grep and Rails’ ActiveSupport::TaggedLogging this helps a lot when debugging an issue that only shows up on your servers.

stream "tail -f #{current_path}/log/#{rails_env}.log"

Handling failing commands

When executing a remote command that fails, Capistrano will abort the deployment process. This makes sense in most cases, but wouldn’t it be nice to have a little more control over the behavior? Let’s say you want to check for the existence of a file on the remote server. One easy way to do this is the test command. It’s exit code will be 0 for success or 1 for failure. Unfortunately this means one of the cases will cause the entire deployment process to fail. To prevent this, all you have to do is to rescue Capistrano::CommandError. A practical example for this technique is this task, that checks if the server is in maintenance mode before running migrations.

namespace :deploy do
  namespace :web do
    desc '[internal] Ensures that web is currently disabled'
    task :ensure do
      begin
        run "test -f #{shared_path}/system/maintenance.html"
      rescue Capistrano::CommandError
        unless fetch(:force, false)
          logger.important 'You should disable the website using deploy:web:disable before doing this'
          exit
        end
      end
    end
  end
end

before 'deploy:migrations', 'deploy:web:ensure'

Conclusion

This is – of course – only a small subset of the useful features Capistrano provides. I hope I showed you one or two you haven’t known before. I encourage you to read through Capistrano’s source code and API documentation to find more of these hidden features.