reservoir_dogs_quentin_tarantino

Advanced server definitions in Capistrano

| 12 Comments

What if you have several servers with different users, ssh-keys, and even port numbers? How to manage all this stuff flexibly ? This tutorial covers poorly documented Capistrano features for advanced servers and roles configuration. Many of them obtained via digging into Capistrano sources.

This article covers following Capistrano topics:

  • roles configuration
  • server configuration
  • The way HOSTS, ROLES, HOSTFILTER, HOSTROLEFILTER variables affect configuration
  • :roles and :hosts settings in the task and run methods
  • How to specify server settings with :hosts option?

Before we start lets create minimal recipe.

Bootstrap

Capistrano loads only two standard recipes by default:

Let’s create our own simple configuration from scratch. Before we choose good server names! :)
servers

Assume we have several servers with following DNS names:

  • mr-white.reservoir.dogs
  • mr-orange.reservoir.dogs
  • mr-blonde.reservoir.dogs

Lets give our servers some roles at config/deploy.rb:

Alternatively you may declare it with server option:

Command invocation

Capistrano has a simple recipe:

Let’s ask servers about current date:

Running a task

Let’s create dead simple task that will show the date. Add following to your config/deploy.rb:

Look for our newly created task in the tasks list:

Let’s play:

So with such configuration task will be executed on all defined servers.

But what if we want to run the task always only for specific role? Configure it as:

And it will be executed as expected:

Environment Variables

Capistrano reacts on four special environment variables that allow you to change server definitions temporary. Let’s read about it from capistrano help:

Let’s define task that should be executed on servers with :nerd role

HOSTS Variable

Let’s run task with following HOSTS:

So command is executed on explicitly specified servers.

But it’s not limited to hosts specified in configuration:

So HOSTS variable overrides server definitions.

ROLES Variable

Let’s run a task with another role:

So ROLES variable also overrides server definitions.

HOSTFILTER Variable

Hosts from HOSTFILTER variable are excluded from servers list for certain task:

So it filters servers, but does not override servers configured for task.

HOSTROLEFILTER Variable

Let’s try to filter servers with another role:

So it also excludes hosts from the servers list for certain task.

For example if we have following task configuration:

Then the command

will be executed only on servers that have :db and :www role.

So let’s summarize:

  • HOSTS and ROLES variables override server definitions.
  • HOSTFILTER and HOSTROLEFILTER variables filter servers.

Thus it’s safe to use HOSTFILTER and HOSTROLEFILTER variables.

Advanced Server Definitions

Running command with custom options

Probably you don’t know that run command accepts the same options as that task itself. So it’s possible to write complex things like this:

So date is executed on all servers. And uname and whoami only on corresponding servers.

Task :roles and :hosts options

If you’ve read Capistrano source code, you may find yet another nifty option called :hosts there.
How does it differ from :roles options? Firstly, :hosts option have higher priority over :roles. Secondary, you always create new server definition object that is not in global server list by specifying server via :hosts options. It means such server won’t be affected with tasks that does not have a role.
It’s really nice option because sometimes you want to execute some commands (notifications for example) on certain hosts that are actually not a target for the code delivery.

Specifying alternative server options

Sometimes you need to specify special server options like username, port, ssh key for each of your servers. And it’s pretty easy when your servers are defined globally:

alternatively you may declare it with server option:

Specifying some server options in task

There is no problem if you use :roles in your task or run method. But what about :hosts task/run options?
At the first look there is an issue because :hosts option is an array of servers name.
But there is a trick available! Look at Capistrano::ServerDefinition

So solution is simple:

So you know how to specify alternative :user and :port options. But what about another server definition options e.g. ssh_options ?

Specifying any server options in task

Do we need to hack find_servers to pass specific :ssh_options for server defined in task? No! recently we found simple solution without hacking: look at server_list_from

So we can pass in :hosts options array of ServerDefinition objects! So solution looks like this:

It is better to define servers as task/run options:

Capistrate the world!

We hope this article will help you to use Capistrano more efficiently.

Share
* Railsware is a premium software development consulting company, focused on delivering great web and mobile applications. Learn more about us.
  • Pingback: This Week’s Ruby News: Ruby 1.9.3p0, OmniAuth 1.0, MagLev 1.0 and More

  • Pingback: Capistrano hosts | Huntelaa

  • http://www.facebook.com/kennejima Kenn Ejima

    Thanks for the great tip!

    One question – if you define the following in a task,

    run “uname”, :roles => :boss
    run “whoami”, :roles => :nerd

    and :boss is only defined for production (missing in staging), it immediately halts there with the message “`shoot’ is only run for servers matching {}, but no servers matched”.

    Is there any way to just skip the missing role and process subsequent commands?

    In my case, I have :slave role in production, but not in staging. I’d like to deploy a special monit config for slave, but other than that everything is the same.

    • Plumber w Plunger

      Actually you should redesign your tasks because it may leads to unexpected behaviour.

      But if you really want to skip command execution for role that is not defined –  it’s easy:

  • http://www.facebook.com/kennejima Kenn Ejima

    Or more in general, is there any way to conditionally run a bulk of commands in a task, based on the current role the commands are being run on? I often feel frustrated that I have to define many redundant tasks with a very little diff, just to separate roles. Not really DRY.

  • Plumber w Plunger

    Or use special Capistrano configuration option:

  • Pingback: Capistrano | Pearltrees

  • Pingback: Tools for Agile: Capistrano | Grosan Flaviu Gheorghe

  • shyam habarakada

    I can’t thank you enough for posting this!

  • Jan Kubát

    Hi, is it possible to do this?

    role :app, “s1″ {
    :deploy_to => “/path/where/to/deploy”
    }

Want to get more of Railsware blog?

RSS FEED

We're always ready to help!

CONTACT US