Using Capistrano

Dan Frost

Issue #177, January 2009

“We will encourage you to develop the three great virtues of a programmer: laziness, impatience, and hubris.”—Larry Wall, Programming Perl

For most programmers, deployment is an area that could do with a touch of laziness. Deploying to a cluster—or even one machine—can be repetitive and tiring. Enter Capistrano, a Ruby deployment tool that makes the task of deploying an application to servers easier by running defined tasks for you on the remote servers.

The Ruby programmers' toolbox contains so many tools for eliminating most of their work, it's fair to say that Ruby programmers are probably some of the laziest. If having all the boring jobs done for you isn't enough, Ruby programmers even contrive to have most of their tools built in one language—Ruby. No bash-make-PHP-Perl combinations. It's all Ruby.

Think of Capistrano as a build system that specializes in running commands remotely on any number of machines. If you have to connect to a half-dozen machines to push updates, or have no quick-and-easy way of rolling back the entire cluster if (or when?) something goes wrong, you need to be a little more lazy.

Capistrano groups tasks in recipes, and the default recipe, which we'll look at in a moment, is very geared toward Rails, running migrations and restarting the Rails server. However, Capistrano's core is not Rails-specific. You can build your own recipes for all your dullest tasks, and you can tweak the Rails recipe to work with whichever language or framework you're using.

Let's take a look at what Capistrano does for Rails deployment, how to build your own tasks and how to push your own application out to 20 servers with just one command.

Capistrano and Rails

Like Rails, Capistrano increasingly is deployed with flavours of Linux and is installed by default in Leopard, so you might not even have to install it. If you do need to, installing Capistrano is as easy as any Ruby gem. Simply run:

sudo gem install -y capistrano

Capistrano has two main commands: cap, which is used for viewing and running the tasks, and capify, which is used to set up a Rails project for use with Capistrano. Assuming you have a Rails project, grab a copy of it, and run capify at the project root:

cd path/to/project capify .

This creates just two files: Capfile and config/deploy.rb. Capfile is to Capistrano as Makefile is to make and Rakefile is to rake. Capistrano expects a Capfile to be present and to contain the tasks or to include a Ruby file that does.

In this case, the Capfile just includes config/deploy.rb, so the latter is the one of interest. The deploy file contains a bunch of settings you need to take care of before running cap, starting with:

set :application, "set your application name here"
set :repository,  "set your repository location here"

If you aren't used to Ruby's syntax, this all will look deceptively like simple configuration. However, because you don't have to use brackets when calling functions in Ruby, each line actually is a call to the set() function in Capistrano's core:

set(:application, "your-app-name")

Set the :application variable to a name without spaces—this will be used to create a deployment directory later. Set :repository to your versioning repository's URL (in this example, we use SVN).

If you have a user name and password for SVN, set them with the lines:

set :scm_username, "svn-username" 
set :scm_password, "svn-password" 

Then, uncomment and set the deployment directory. If the deploy_to doesn't exist on your deployment server, Capistrano creates it:

set :deploy_to, "/path/to/doc/root/www/#{application}"

Here, we're using the application variable we set previously to set part of the deploy_to variable. This is all standard Ruby syntax, available in all Capistrano scripts, making this way of working extremely powerful and a little less cumbersome than a hodge-podge of obscure syntaxes.

Finally, we need to set the servers that will host the deployment. You can add as many servers as you like, and the server name just has to be something that SSH understands—for example:

role :app, "app-server-1", "app-server-2", "app-server-3"
role :web, "192.168.1.123"
role :db,  "db-server-1", :primary => true

If you're just testing out Capistrano, it's worth setting the deployment location as your working machine; that way, you can learn without moving between machines:

role :app, "me@my-local-ip"

Now we're ready to ask Capistrano to set up the deployment location using the command:

cap deploy:setup

When you run this, Capistrano starts showing you what it's doing. This helps when debugging Capfiles, and it reassures you that you're doing the right thing. Whenever you connect to another server, you'll be prompted for the password, as usual, after which Capistrano will run a bunch of other commands.

After deploy:setup, the deployment directory now contains some extra directories that will allow cap to push new versions, do rollbacks and so on:

myapp/
      releases/ shared/log shared/pids shared/system

Next, we get on and deploy the application. Capistrano will check out the source, put it into releases and create a symlink to it called current:

cap deploy:cold

After this has run, take another look in the deployment location:

# current@ -> /www/captest/myapp/releases/20080614144520

This a “cold” deployment, meaning tasks that are one-time tasks are run. To deploy the application in the future, you simply use the deploy task:

cap deploy

When you've run either deploy:cold or deploy, have a look in the deployment directory and find where your source code fits into Capistrano's way of deploying things.

The deploy task replaces logging in to the server, getting the source, setting up any databases and restarting the servers. Run it a few times, and get used to that lazy feeling!

Finding More

To deploy our application, we used only deploy:setup, deploy:cold and deploy. The recipe has a lot more in it. To see all the available tasks, run:

cap -Tv

Much like rake -T, this lists all the tasks with their documentation. If you've run deploy a few times, play with either of the rollback or rollback_code tasks.

Each time you roll back, Capistrano simply points the symlink to the previous deployment's directory. Rollbacks can be run repeatedly until you find the stable version you want:

cap deploy:rollback_code

Your Own Tasks

Once you get Capistrano working on a Rails project, it's easy to see how it could help make your life really lazy. The same kind of tasks that wrap around Rails-specific commands can contain pretty much any command.

When you run Capistrano tasks, like deploy, you'll see various SSH commands and responses scroll by. If you have several servers, the responses will come back from multiple servers as Capistrano runs your tasks across as many machines as you need.

The potential uses of this are huge—checking disk space, copying live data from clusters and running maintenance tasks—so how can we build our own tasks?

Tasks in Capistrano are defined with the following syntax:

desc "Short description here..."
task :name_of_function, :roles => :servers do
        # tasks is in here...
end

Ruby's elegant syntax often makes things confusingly simple, so let's pick it apart. The first line provides some documentation that is output when you run the following on the command line (still from the root of your project):

cap -Tv

Ruby can cope without brackets when calling functions, so the second line actually is a call to Capistrano's task function.

The first argument is the new task's name (name_of_function). The second is the set of machines on which the task will be run; this can be either :servers, :app, :db or any other collection of servers you have.

The last part, starting at do, is an anonymous function, which means that everything between do and end is executed when your task is run. You may have come across anonymous functions in JavaScript.

A very simple task would be to run df -h on the remote servers to check on disk space. This isn't going to change anything on your servers, so you should feel safe running it:

desc "Check disk space"
task :diskspace, :roles => :servers do
        run 'df -h'
end

The run function simply runs the command on the remote servers. You can replace this with sudo, which also does what it sounds like—runs remote commands under sudo:

desc "Who hasn't been cleaning out their home directories?"
task :home_disk_usage, :roles => :servers do
        sudo 'du -sh /home/*'
end

If you have capified a project as we did on the Rails project in the previous section, you even can add your own custom tasks to the standard Rails recipe and change the behaviour of the Rails recipe itself. This lets you get Capistrano working just as you need it to work, and it's is good for those commands you never can remember how to run!

To add your own tasks to a capified Rails project, add them to config/deploy.rb using the task syntax described above. Once you have added a task, run cap -Tv to check whether your task was found, and then run the task as you would any other.

Tasks can call each other just like functions can, so complex tasks can be broken down into simple tasks that will keep your custom Capistrano recipes “DRY”. Tasks can call each other using the normal Rails function call:

task :home_disk_usage, :roles => :servers do
        vhosts_disk_usage
        run "ls /home/"
end

You'll probably want your customised tasks to know the location in the filesystem where your project is being deployed. This is a matter of using the configuration variables we set right at the beginning, which can be done using the Ruby syntax:

run "tar czf ~/snapshot.tgz #{release_path}"

If you need additional variables, you can set them using the same syntax as before:

set :foo, "bar"

Alternatively, you can prompt the user for the variables by using the set function, but with a slightly different usage:

set(:deploy_version) do
        Capistrano::CLI.ui.ask "What version is this? "
end

The variables are used in the same way, no matter which method is used to set them.

All this Ruby should start falling into place, and by this point, you'll start thinking of Capistrano as a Ruby framework rather than a standalone application or script. If Ruby is new to you, keep going—it'll start dropping into place soon.

Finally, it's nice to keep things neat as well as DRY. All of the Rails recipes are found in the deploy namespace, which you'll notice when you run cap -Tv. Namespaces allow you to group tasks together, and this can be done by wrapping the tasks in the namespace command:

namespace :our_tasks do
  desc "The default task"
  task :default do
    restart
end
  
  desc "Empty logs"
  task :empty_logs do
        # ...
  end
end

When you run cap -Tv, you'll see these neatly grouped:

cap our_tasks            # The default task
cap our_tasks:empty_logs # Empty logs

Customising the Rails Recipe

Making new Capistrano tasks is straightforward, but the Rails recipe we used earlier probably contains 90% or more of what you need. In this case, it's best to customise the recipe rather than create one from scratch. We can do this by overriding specific tasks to customise the corresponding behaviour of the recipe.

I discovered this when trying Capistrano on our internal makefiles, which is where I do most of our code file management, database versioning and installation configuration loads. We use these for pretty much everything that isn't committing or editing files, so the idea that we also could deploy really quickly using Capistrano was just too tempting.

If you've read this far but are thinking, “cool, but we're not about to migrate to Rails”, customisation will make sense for you because you can override the tasks that try to do Rails-specific things.

First, try capify on a non-Rails project, but make sure you have a config/ directory where capify can put its deploy.rb file. Once capify has run, you can start trying the various cap deploy tasks we did above, but it all goes wrong when Capistrano starts whining about the Rails server not being present and about a Rakefile not being present.

This is because one of the tasks, deploy:restart, tries to restart the Rails server. Another of the tasks tries to run rake db:migrate. Your project probably will support neither of these, so you should override it by adding the following to config/deploy.rb:

desc "Do nothing"
deploy.task :restart, :roles => :app do
  # ...do what you like here... 
end

Intuitively, this is overriding the restart task in the deploy namespace, and everything inside the task (everything from do to end) can be edited as normal. You might want to restart your Apache server instead of the Rails server:

desc "Do nothing"
deploy.task :restart, :roles => :app do
        sudo '/etc/init.d/restart'
end

When you run cap deploy:cold, the Rails migrations are run to create the database. We override this to run our equivalent, which is:

deploy.task :migrate, :roles => :app do
        run "make data"
end

Conclusion

Capistrano provides a really simple way of deploying an application. It also can be used for anything involving remote servers: monitoring, arbitrary tasks, creating ad hoc backups and so on.

Thanks to Ruby's elegance, Capistrano can be extended in pretty much every way. The Rails recipe can be honed for non-Rails applications, and adding whole new recipes involves very little Ruby knowledge.

Finally, to make things even quicker, use SSH identities so you don't even have to log in to the remote servers. If you want to keep your identities somewhere nonstandard, simply add the following to your deploy.rb file:

ssh_options[:keys] = "/path/to/identity_file" 

This way, you can deploy your app using cap deploy and nothing else—now you really can master laziness.

Dan Frost is Technical Director of 3ev, a Web app development company in Brighton, UK. Alongside his work as a developer and technical architect in PHP, Java and all the usual stuff, he writes articles on Cloud computing, Rails and Web 2.0 technologies.