Advanced server definitions in Capistrano

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.

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.

Ruby

1

2

3

task:log_deploy,:hosts=>['history.reservoir.dogs']do

run"cat '#{Time.now},#{real_revision}' >> deploy.log"

end

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:

Ruby

1

2

3

4

5

6

7

8

9

10

11

12

13

role:app,'app1.reservoir.dogs','app2.reservoir.dogs',{

:user=>'nerd',

:ssh_options=>{

:keys=>'./keys/nerd.pem'

}

}

role:db,'db.reservoir.dogs',{

:user=>'boss',

:ssh_options=>{

:keys=>'./keys/boss.pem'

}

}

alternatively you may declare it with server option:

Ruby

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

server'app1.reservoir.dogs',:app,{

:user=>'nerd1',

:ssh_options=>{

:keys=>'./keys/nerd.pem'

}

}

server'app2.reservoir.dogs',:app{

:user=>'nerd2',

:ssh_options=>{

:keys=>'./keys/nerd.pem'

}

}

server'db.reservoir.dogs',:db{

:user=>'boss',

:ssh_options=>{

:keys=>'./keys/boss.pem'

}

}

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

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:

1

2

3

4

task:shoot do

run"uname",:roles=>:boss iftop.roles.key?(:boss)

run"whoami",:roles=>:nerd

end

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.