{"id":887,"date":"2011-11-02T18:07:52","date_gmt":"2011-11-02T16:07:52","guid":{"rendered":"http:\/\/blog.railsware.com\/?p=887"},"modified":"2021-08-12T17:36:35","modified_gmt":"2021-08-12T14:36:35","slug":"advanced-server-definitions-in-capistrano","status":"publish","type":"post","link":"https:\/\/railsware.com\/blog\/advanced-server-definitions-in-capistrano\/","title":{"rendered":"Advanced server definitions in Capistrano"},"content":{"rendered":"\n<p>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.<\/p>\n\n\n\n<p>This article covers following Capistrano topics:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li><em>roles<\/em> configuration<\/li><li><em>server<\/em> configuration<\/li><li>The way <em>HOSTS<\/em>, <em>ROLES<\/em>, <em>HOSTFILTER<\/em>, <em>HOSTROLEFILTER<\/em> variables affect configuration<\/li><li><em>:roles<\/em> and <em>:hosts<\/em> settings in the task and run methods<\/li><li>How to specify server settings with <em>:hosts<\/em> option?<\/li><\/ul>\n\n\n\n<p>Before we start lets create minimal recipe.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Bootstrap<\/h3>\n\n\n\n<pre class=\"wp-block-preformatted\">$ gem install capistrano\n$ mkdir test\n$ cd test\n$ capify .<\/pre>\n\n\n\n<p>Capistrano loads only two standard recipes by default:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">$ cap -T\ncap invoke    # Invoke a single command on the remote servers.\ncap shell     # Begin an interactive Capistrano session.<\/pre>\n\n\n\n<p>Let&#8217;s create our own simple configuration from scratch. Before we choose good server names! :)<br><img decoding=\"async\" src=\"\/\/upload.wikimedia.org\/wikipedia\/en\/thumb\/9\/9f\/Reservoirdog.jpg\/300px-Reservoirdog.jpg\" alt=\"servers\"><\/p>\n\n\n\n<p>Assume we have several servers with following DNS names:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>mr-white.reservoir.dogs<\/li><li>mr-orange.reservoir.dogs<\/li><li>mr-blonde.reservoir.dogs<\/li><\/ul>\n\n\n\n<p>Lets give our servers some roles at <em>config\/deploy.rb<\/em>:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">  role :dog, 'mr-white.reservoir.dogs', 'mr-orange.reservoir.dogs', 'mr-blonde.reservoir.dogs'\n  role :boss, 'mr-white.reservoir.dogs'\n  role :nerd, 'mr-orange.reservoir.dogs', 'mr-blonde.reservoir.dogs'<\/pre>\n\n\n\n<p>Alternatively you may declare it with <em>server<\/em> option:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">  server 'mr-white.reservoir.dogs',  :dog, :boss\n  server 'mr-orange.reservoir.dogs', :dog, :nerd\n  server 'mr-blonde.reservoir.dogs', :dog, :nerd<\/pre>\n\n\n\n<!--more-->\n\n\n\n<h3 class=\"wp-block-heading\">Command invocation<\/h3>\n\n\n\n<p>Capistrano has a simple recipe:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">cap invoke # Invoke a single command on the remote servers.<\/pre>\n\n\n\n<p>Let&#8217;s ask servers about current date:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">$ cap invoke COMMAND=date\n  * executing `invoke'\n  * executing \"date\"\n    servers: [\"mr-white.reservoir.dogs\", \"mr-orange.reservoir.dogs\", \"mr-blonde.reservoir.dogs\"]\n    [mr-white.reservoir.dogs] executing command\n    [mr-orange.reservoir.dogs] executing command\n    [mr-blonde.reservoir.dogs] executing command\n ** [out :: mr-white.reservoir.dogs] Fri Oct 28 10:23:50 EDT 2011\n ** [out :: mr-orange.reservoir.dogs] Fri Oct 28 10:23:50 EDT 2011\n ** [out :: mr-blonde.reservoir.dogs] Fri Oct 28 10:23:50 EDT 2011\n    command finished in 614ms<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Running a task<\/h3>\n\n\n\n<p>Let&#8217;s create dead simple task that will show the date. Add following to your <em>config\/deploy.rb<\/em>:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">  desc \"Show server' date\"\n  task :show_date do\n    run('date')\n  end<\/pre>\n\n\n\n<p>Look for our newly created task in the tasks list:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">$ cap -T\ncap invoke    # Invoke a single command on the remote servers.\ncap shell     # Begin an interactive Capistrano session.\ncap show_date # Show server' date<\/pre>\n\n\n\n<p>Let&#8217;s play:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">$ cap show_date\n  * executing `show_date'\n  * executing \"date\"\n    servers: [\"mr-white.reservoir.dogs\", \"mr-orange.reservoir.dogs\", \"mr-blonde.reservoir.dogs\"]\n ** [out :: mr-white.reservoir.dogs] Fri Oct 28 10:23:50 EDT 2011\n ** [out :: mr-orange.reservoir.dogs] Fri Oct 28 10:23:50 EDT 2011\n ** [out :: mr-blonde.reservoir.dogs] Fri Oct 28 10:23:50 EDT 2011<\/pre>\n\n\n\n<p>So with such configuration task will be executed on all defined servers.<\/p>\n\n\n\n<p>But what if we want to run the task always only for specific role? Configure it as:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">  desc \"Show server' date\"\n  task :show_date, :roles =&gt; [:nerd]  do\n    run('date')\n  end<\/pre>\n\n\n\n<p>And it will be executed as expected:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">$ cap show_date\n  * executing `show_date'\n  * executing \"date\"\n    servers: [\"mr-orange.reservoir.dogs\", \"mr-blonde.reservoir.dogs\"]\n ** [out :: mr-orange.reservoir.dogs] Fri Oct 28 10:23:50 EDT 2011\n ** [out :: mr-blonde.reservoir.dogs] Fri Oct 28 10:23:50 EDT 2011<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Environment Variables<\/h3>\n\n\n\n<p>Capistrano reacts on four special environment variables that allow you to change server definitions temporary. Let&#8217;s read about it from capistrano help:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">$ cap -H\n\n  HOSTS\n        Execute the tasks against this comma-separated list of hosts.\n        Effectively, this makes the host(s) part of every roles.\n\n  HOSTFILTER\n        Execute tasks against this comma-separated list of host, but only if the\n        host has the proper role for the task.\n\n  HOSTROLEFILTER\n       Execute tasks against the hosts in this comma-separated list of roles,\n       but only if the host has the proper role for the task.\n\n  ROLES\n      Execute tasks against this comma-separated list of roles.  Hosts which\n      do not have the right roles will be skipped.<\/pre>\n\n\n\n<p>Let&#8217;s define task that should be executed on servers with <em>:nerd<\/em> role<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">  desc \"Show server' date\"\n  task :show_date, :roles =&gt; [:nerd]  do\n    run('date')\n  end<\/pre>\n\n\n\n<h4 class=\"wp-block-heading\">HOSTS Variable<\/h4>\n\n\n\n<p>Let&#8217;s run task with following HOSTS:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">$ cap show_date HOSTS=mr-white.reservoir.dogs,mr-orange.reservoir.dogs\n  * executing `show_date'\n  * executing \"date\"\n    servers: [\"mr-white.reservoir.dogs\", \"mr-orange.reservoir.dogs\"]\n...<\/pre>\n\n\n\n<p>So command is executed on explicitly specified servers.<\/p>\n\n\n\n<p>But it&#8217;s not limited to hosts specified in configuration:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">cap show_date HOSTS=mr-white.reservoir.dogs,google.com\n  * executing `show_date'\n  * executing \"date\"\n    servers: [\"mr-white.reservoir.dogs\", \"google.com\"]\n...<\/pre>\n\n\n\n<p>So HOSTS variable <em>overrides<\/em> server definitions.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">ROLES Variable<\/h4>\n\n\n\n<p>Let&#8217;s run a task with another role:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">$ cap show_date ROLES=boss\n  * executing `show_date'\n  * executing \"date\"\n    servers: [\"mr-white.reservoir.dogs\"]\n...<\/pre>\n\n\n\n<p>So ROLES variable also <em>overrides<\/em> server definitions.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">HOSTFILTER Variable<\/h4>\n\n\n\n<p>Hosts from HOSTFILTER variable are excluded from servers list for certain task:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">$ cap show_date HOSTFILTER=mr-orange.reservoir.dogs,google.com\n  * executing `show_date'\n  * executing \"date\"\n    servers: [\"mr-orange.reservoir.dogs\"]\n...<\/pre>\n\n\n\n<p>So it <em>filters<\/em> servers, but <em>does not override<\/em> servers configured for task.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">HOSTROLEFILTER Variable<\/h4>\n\n\n\n<p>Let&#8217;s try to filter servers with another role:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">$ cap show_date HOSTROLEFILTER=boss\n  * executing `show_date'\n  * executing \"date\"\n`show_date' is only run for servers matching {:roles=&gt;:nerd}, but no servers matched<\/pre>\n\n\n\n<p>So it also excludes hosts from the servers list for certain task.<\/p>\n\n\n\n<p>For example if we have following task configuration:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">  task :show_date, :roles =&gt; [:app, :db, :www, :backup]  do\n    ...\n  end<\/pre>\n\n\n\n<p>Then the command<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">$ cap show_date HOSTROLEFILTER=db,www,another_role<\/pre>\n\n\n\n<p>will be executed <em>only<\/em> on servers that have <em>:db<\/em> and <em>:www<\/em> role.<\/p>\n\n\n\n<p>So let&#8217;s summarize:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>HOSTS and ROLES variables <em>override<\/em> server definitions.<\/li><li>HOSTFILTER and HOSTROLEFILTER variables <em>filter<\/em> servers.<\/li><\/ul>\n\n\n\n<p>Thus it&#8217;s safe to use HOSTFILTER and HOSTROLEFILTER variables.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Advanced Server Definitions<\/h3>\n\n\n\n<h4 class=\"wp-block-heading\">Running command with custom options<\/h4>\n\n\n\n<p>Probably you don&#8217;t know that <em>run<\/em> command accepts the same options as that task itself. So it&#8217;s possible to write complex things like this:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">task :shoot do\n  run \"date\"\n  run \"uname\",  :roles =&gt; :boss\n  run \"whoami\", :roles =&gt; :nerd\nend<\/pre>\n\n\n\n<pre class=\"wp-block-preformatted\">$ cap shoot\n  * executing `shoot'\n  * executing \"date\"\n    servers: [\"mr-white.reservoir.dogs\", \"mr-orange.reservoir.dogs\", \"mr-blonde.reservoir.dogs\"]\n    [mr-orange.reservoir.dogs] executing command\n    [mr-white.reservoir.dogs] executing command\n    [mr-blonde.reservoir.dogs] executing command\n ** [out :: mr-white.reservoir.dogs] Fri Oct 28 12:00:19 EDT 2011\n ** [out :: mr-orange.reservoir.dogs] Fri Oct 28 12:00:19 EDT 2011\n ** [out :: mr-blonde.reservoir.dogs] Fri Oct 28 12:00:19 EDT 2011\n    command finished in 737ms\n  * executing \"uname\"\n    servers: [\"mr-white.reservoir.dogs\"]\n    [mr-white.reservoir.dogs] executing command\n ** [out :: mr-white.reservoir.dogs] Linux\n    command finished in 479ms\n  * executing \"whoami\"\n    servers: [\"mr-orange.reservoir.dogs\", \"mr-blonde.reservoir.dogs\"]\n    [mr-orange.reservoir.dogs] executing command\n    [mr-blonde.reservoir.dogs] executing command\n ** [out :: mr-orange.reservoir.dogs] mongrel\n ** [out :: mr-blonde.reservoir.dogs] mongrel\n    command finished in 746ms<\/pre>\n\n\n\n<p>So <code>date<\/code> is executed on all servers. And <code>uname<\/code> and <code>whoami<\/code> only on corresponding servers.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Task :roles and :hosts options<\/h4>\n\n\n\n<p>If you&#8217;ve read Capistrano source code, you may find yet another nifty option called <em>:hosts<\/em> there.<br>How does it differ from :roles options? Firstly, :hosts option have higher priority over :roles. Secondary, you always create new server definition object that <em>is not in global<\/em> server list by specifying server via :hosts options. It means such server won&#8217;t be affected with tasks that does not have a role.<br>It&#8217;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.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">task :log_deploy, :hosts =&gt; ['history.reservoir.dogs'] do\n  run \"cat '#{Time.now},#{real_revision}' &gt;&gt; deploy.log\"\nend<\/pre>\n\n\n\n<h4 class=\"wp-block-heading\">Specifying alternative server options<\/h4>\n\n\n\n<p>Sometimes you need to specify special server options like username, port, ssh key for each of your servers. And it&#8217;s pretty easy when your servers are defined globally:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">role :app, 'app1.reservoir.dogs', 'app2.reservoir.dogs', {\n  :user =&gt; 'nerd',\n  :ssh_options =&gt; {\n    :keys =&gt; '.\/keys\/nerd.pem'\n  }\n}\n\nrole :db, 'db.reservoir.dogs', {\n  :user =&gt; 'boss',\n  :ssh_options =&gt; {\n    :keys =&gt; '.\/keys\/boss.pem'\n  }\n}<\/pre>\n\n\n\n<p>alternatively you may declare it with <em>server<\/em> option:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">server 'app1.reservoir.dogs', :app, {\n  :user =&gt; 'nerd1',\n  :ssh_options =&gt; {\n    :keys =&gt; '.\/keys\/nerd.pem'\n  }\n}\n\nserver 'app2.reservoir.dogs', :app {\n  :user =&gt; 'nerd2',\n  :ssh_options =&gt; {\n    :keys =&gt; '.\/keys\/nerd.pem'\n  }\n}\n\nserver 'db.reservoir.dogs', :db {\n  :user =&gt; 'boss',\n  :ssh_options =&gt; {\n    :keys =&gt; '.\/keys\/boss.pem'\n  }\n}<\/pre>\n\n\n\n<h4 class=\"wp-block-heading\">Specifying some server options in task<\/h4>\n\n\n\n<p>There is no problem if you use :roles in your task or <em>run<\/em> method. But what about :hosts task\/run options?<br>At the first look there is an issue because :hosts option is an array of servers name.<br>But there is a trick available! Look at <a href=\"https:\/\/github.com\/capistrano\/capistrano\/blob\/2.8.0\/lib\/capistrano\/server_definition.rb#L16\">Capistrano::ServerDefinition<\/a><\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">module Capistrano\n  class ServerDefinition\n    def initialize(string, options={})\n      @user, @host, @port = string.match(\/^(?:([^;,:=]+)@|)(.*?)(?::(\\d+)|)$\/)[1,3]\n      ...\n    end\n  end\nend<\/pre>\n\n\n\n<p>So solution is simple:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">run(\"date\", :hosts =&gt; ['nerd1@app1.reservoir.dogs:2201', 'nerd2@app1.reservoir.dogs:2201'])\n\ntask(:show_date, :hosts =&gt; ['nerd1@app1.reservoir.dogs:2201', 'nerd2@app1.reservoir.dogs:2201']) do\n  run(\"date\")\nend\n<\/pre>\n\n\n\n<p>So you know how to specify alternative <em>:user<\/em> and <em>:port<\/em> options. But what about another server definition options e.g. ssh_options ?<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Specifying any server options in task<\/h4>\n\n\n\n<p>Do we need to hack <a href=\"https:\/\/github.com\/capistrano\/capistrano\/blob\/2.8.0\/lib\/capistrano\/configuration\/servers.rb#L44\">find_servers<\/a> to pass specific :ssh_options for server defined in task? No! recently we found simple solution without hacking: look at <a href=\"https:\/\/github.com\/capistrano\/capistrano\/blob\/2.8.0\/lib\/capistrano\/configuration\/servers.rb#L95\">server_list_from<\/a><\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">  def server_list_from(hosts)\n    hosts = hosts.split(\/,\/) if String === hosts\n    hosts = build_list(hosts)\n    hosts.map { |s| String === s ? ServerDefinition.new(s.strip) : s }\n  end<\/pre>\n\n\n\n<p>So we can pass in <em>:hosts<\/em> options array of <em>ServerDefinition<\/em> objects! So solution looks like this:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">run(\"date\", :hosts =&gt; [\n  Capistrano::ServerDefinition.new('app1.reservoir.dogs', {\n    :user =&gt; 'nerd1',\n    :port =&gt; 2201,\n    :ssh_options =&gt; {\n      :keys =&gt; '.\/keys\/nerd.pem'\n    }\n  }),\n  Capistrano::ServerDefinition.new('app2.reservoir.dogs', {\n    :user =&gt; 'nerd2',\n    :port =&gt; 2202,\n    :ssh_options =&gt; {\n      :keys =&gt; '.\/keys\/nerd.pem'\n    }\n  })\n])\n\ntask(:show_date, :hosts =&gt; [\n  Capistrano::ServerDefinition.new('app1.reservoir.dogs', {\n    :user =&gt; 'nerd1',\n    :port =&gt; 2201,\n    :ssh_options =&gt; {\n      :keys =&gt; '.\/keys\/nerd.pem'\n    }\n  }),\n  Capistrano::ServerDefinition.new('app2.reservoir.dogs', {\n    :user =&gt; 'nerd2',\n    :port =&gt; 2202,\n    :ssh_options =&gt; {\n      :keys =&gt; '.\/keys\/nerd.pem'\n    }\n  })\n]) do\n  run(\"date\")\nend\n<\/pre>\n\n\n\n<p>It is better to define servers as task\/run options:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">set(:boss_host, {\n  Capistrano::ServerDefinition.new('boss.reservoir.dogs', {\n    :user =&gt; 'boss',\n    :port =&gt; 2222,\n    :ssh_options =&gt; {\n      :keys =&gt; '.\/keys\/boss.pem'\n    }\n})\n\nrun(\"date\", :hosts =&gt; [ :boss_host ]<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Capistrate the world!<\/h3>\n\n\n\n<p>We hope this article will help you to use Capistrano more efficiently.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>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 configurationserver configurationThe way HOSTS,&#8230;<\/p>\n","protected":false},"author":19,"featured_media":9438,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[3],"tags":[],"coauthors":["Andriy Yanko"],"class_list":["post-887","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-development"],"acf":[],"aioseo_notices":[],"categories_data":[{"name":"Engineering","link":"https:\/\/railsware.com\/blog?category=development"}],"post_thumbnails":"\/\/upload.wikimedia.org\/wikipedia\/en\/thumb\/9\/9f\/Reservoirdog.jpg\/300px-Reservoirdog.jpg","amp_enabled":true,"_links":{"self":[{"href":"https:\/\/railsware.com\/blog\/wp-json\/wp\/v2\/posts\/887","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/railsware.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/railsware.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/railsware.com\/blog\/wp-json\/wp\/v2\/users\/19"}],"replies":[{"embeddable":true,"href":"https:\/\/railsware.com\/blog\/wp-json\/wp\/v2\/comments?post=887"}],"version-history":[{"count":53,"href":"https:\/\/railsware.com\/blog\/wp-json\/wp\/v2\/posts\/887\/revisions"}],"predecessor-version":[{"id":14016,"href":"https:\/\/railsware.com\/blog\/wp-json\/wp\/v2\/posts\/887\/revisions\/14016"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/railsware.com\/blog\/wp-json\/wp\/v2\/media\/9438"}],"wp:attachment":[{"href":"https:\/\/railsware.com\/blog\/wp-json\/wp\/v2\/media?parent=887"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/railsware.com\/blog\/wp-json\/wp\/v2\/categories?post=887"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/railsware.com\/blog\/wp-json\/wp\/v2\/tags?post=887"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/railsware.com\/blog\/wp-json\/wp\/v2\/coauthors?post=887"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}