#133 Capistrano Tasks (revised)
- Download:
- source codeProject Files in Zip (40.5 KB)
- mp4Full Size H.264 Video (32.5 MB)
- m4vSmaller H.264 Video (14.6 MB)
- webmFull Size VP8 Video (13.3 MB)
- ogvFull Size Theora Video (36.8 MB)
Capistrano can be quite puzzling when you first start using it. It’s easy to take a basic deployment recipe and fill in the blanks with the relevant settings but it’s harder to write a script from scratch or to know what’s going on behind the scenes when a script runs. If this sounds familiar then this is the episode for you.
Things won’t really start to click unless you know what Capistrano is all about. It’s based on tasks though this isn’t always apparent when we look at the typical deployment recipe. We’ll start off with a simple task but first we’ll need a Rails application to work with.
$ rails new store
Next we’ll uncomment the reference to the Capistrano gem in our new application’s gemfile then run bundle
to install it.
# Deploy with Capistrano gem 'capistrano'
To set up Capistrano in our application we need to run the capify
command.
$ capify . [add] writing './Capfile' [add] writing './config/deploy.rb' [done] capified!
This command generates a deploy.rb
file in the config
directory and we can customize the way the deployment works by modifying this file.
Our First Capistrano Task
By default deploy.rb
comes with some initial settings and comments but we’ll remove these and start with something simple so that we can demonstrate tasks. Creating a Capistrano task is similar to creating a Rake task, except that we use a task
method.
task :hello do puts "Hello World" end
To run this task we call cap and pass in the name of the task.
$ cap hello * executing `hello' Hello World
Depending on how your shell environment is set up you might need to prefix this command with bundle exec
. Tasks are often chained together. To demonstrate this we’ll create a new goodbye task. If we want it to run after hello
we can call it like a method.
task :hello do puts "Hello World" goodbye end task :goodbye do puts "Goodbye World" end
When we run cap hello
again this will run the hello
task followed by the goodbye task.
$ cap hello * executing `hello' Hello World * executing `goodbye' Goodbye World
Another way to do this is to use callbacks. Instead of calling goodbye directly from hello
we can make a callback by using the after
method.
task :hello do puts "Hello World" end task :goodbye do puts "Goodbye World" end after :hello, :goodbye
When we run cap hello
now we see the same output as before.
Tasks can also have settings. To create a setting we use set. We can then access the setting in any task.
set :recipient, "Ruby" task :hello do puts "Hello #{recipient}" end task :goodbye do puts "Goodbye #{recipient}" end after :hello, :goodbye
Another way to access a setting is to use fetch
. The advantage of this approach is that we can pass in a default value as a second argument.
set :recipient, "Ruby" task :hello do puts "Hello #{fetch(:recipient, "World")}" end
If the recipient
setting isn’t defined the task will now use the default value of “World”.
Running Commands on The Server
So far we’ve been executing code on our local machine but Capistrano is designed to run commands remotely. To do this we’ll need to specify a server, which we do by calling server
. We need to pass this the address of a server and a role. We’ll explain roles later; for now we’ll just set this to web
. We also need to set the user the script should connect to the server as and we’ll set this to the deployer
user.
server "178.xx.xxx.xxx", :web set :user, "deployer" set :recipient, "Ruby" task :hello do puts "Hello #{fetch(:recipient, "World")}" run "echo 'Hello World' > ~/hello" end task :goodbye do puts "Goodbye #{recipient}" end after :hello, :goodbye
We can run a command on the remote server by calling run
in a task and passing it a command. We’ve done it this now in our hello
task where we echo the text “Hello World” into a file in the deployer
’s home directory. We’ve already set up a server with this user and there’s more information about this in episode 335. When we run cap hello
now it will SSH into the server and execute that command there.
$ cap hello * executing `hello' Hello Ruby * executing "echo 'Hello World' > ~/hello" servers: ["178.xxx.xxx.xxx"] [178.xxx.xxx.xxx] executing command [178.xxx.xxx.xxx] sh -c 'echo '\''Hello World'\'' > ~/hello' command finished in 815ms triggering after callbacks for `hello' * executing `goodbye' Goodbye Ruby
We can check to see if this has worked by logging in to the server and checking the contents of that file.
$ ssh deployer@178.xxx.xxx.xxx 'cat ~/hello' Hello World
If we need to run a command as the root user we can do so. Let’s say that we want to copy the file that we’ve created to the server’s root directory. We need to run this command under sudo
and we can do this in Capistrano by prefixing the command with #{sudo}
, like this:
task :hello do puts "Hello #{fetch(:recipient, "World")}" run "#{sudo} cp ~/hello /hello" end
There’s a potential problem here. If we want the password input to work properly then we might need to use the default_run_options
command in our script.
server "178.xxx.xxx.xxx", :web set :user, "deployer" set :recipient, "Ruby" default_run_options[:pty] = true task :hello do puts "Hello #{fetch(:recipient, "World")}" run "#{sudo} cp ~/hello /hello" end
With this in place if the server requests a password this request will be passed on to the client machine’s shell. We can see this if we run cap hello
again.
$ cap hello * executing `hello' Hello Ruby * executing "sudo -p 'sudo password: ' cp ~/hello /hello" servers: ["178.xxx.xxx.xxx"] [178.xxx.xxx.xxx] executing command [178.xxx.xxx.xxx] sh -c 'sudo -p '\''sudo password: '\'' cp ~/hello /hello' Password: ** [out :: 178.xxx.xxx.xxx] command finished in 5740ms triggering after callbacks for `hello' * executing `goodbye' Goodbye Ruby
Working With Multiple Servers
Capistrano is designed to work with multiple servers. We can specify more than one server with the server
command and if we do so each run command will run its command on each server listed. If we have multiple servers we generally want to run different commands on each one and this is where roles come in. For example let’s say that our hello
task deals with a separate database server. We can set the roles that the task should run for so we’ll set it to run for just db servers.
server "178.xxx.xxx.xxx", :web set :user, "deployer" set :recipient, "Ruby" default_run_options[:pty] = true task :hello, roles: :db do puts "Hello #{fetch(:recipient, "World")}" run "#{sudo} cp ~/hello /hello" end
As we haven’t defined a server with a db
role we’ll see an error when we run cap hello
now and the command won’t be run.
$ cap hello * executing `hello' Hello Ruby * executing "sudo -p 'sudo password: ' cp ~/hello /hello" `hello' is only run for servers matching {:roles=>:db}, but no servers matched
We’ll need to add the db
role to our server for the command to be executed.
server "178.xxx.xxx.xxx", :web, :db
Capistrano uses three different roles: web
, app
and db
. The web
role is for an Apache or Nginx server, app
is where our application is hosted and db
is for the server that hosts our MySQL or PostgreSQL database. We aren’t limited to just these three roles, though. We might have a queueing server, a caching server or any other server our application’s deployment requires and we can specify our own role names for these. One important thing about the database role is we should pass the primary
option and set it to true
. This marks it as the primary database server and therefore the one that database migrations will be run against.
server "178.xxx.xxx.xxx", :web, :db, primary: true
There’s another way to define server roles. Instead of using server
we can use a role
method like this:
role :web, "178.xxx.xxx.xxx" role :app, "178.xxx.xxx.xxx" role :db, "178.xxx.xxx.xxx", primary: true
Here we pass the name of the role and the server and list each one individually. The server method is more concise but you’re free to choose the approach that you prefer.
Capistrano’s Built-in Tasks
Now that we know the basic concepts of Capistrano we can get a better idea of what’s going on when we use it to deploy an application. To show this we’ll take a look at Capistrano’s source code. Under its lib/capistrano/recipes
directory there’s a deploy.rb
file. This contains all the deployment-related recipes and these are the tasks that are provided by Capistrano out of the box. These are all defined under a deploy
namespace and they include a default
task which is the one that runs when we run cap deploy
.
namespace :deploy do task :default do update restart end # Other tasks omitted. end
This task triggers two others, update and restart. These are defined in the same file. Let’s look at update.
namespace :deploy do task :update do transaction do update_code create_symlink end end end
This task starts by creating a new transaction. We haven’t discussed transactions in this episode but they provide a way of rolling back changes if a task fails. The update task then calls two others called update_code and create_symlink. This removes the application’s current path, if it exists, then re-symlinks it based on the latest release path.
namespace :deploy do task :create_symlink, :except => { :no_release => true } do on_rollback do if previous_release run "rm -f #{current_path}; ln -s #{previous_release} #{current_path}; true" else logger.important "no previous release to rollback to, rollback of symlink skipped" end end run "rm -f #{current_path} && ln -s #{latest_release} #{current_path}" end end
So what we have is a chain of tasks which do various things and it’s easy to see here exactly what happens when we run cap deploy
. You might wonder where values like previous_release
and latest_release
come from. They’re defined at the top of the file through a method called _cset
which sets default values for these and a other settings if they haven’t already been defined.
_cset(:releases_path) { File.join(deploy_to, version_dir) } _cset(:shared_path) { File.join(deploy_to, shared_dir) } _cset(:current_path) { File.join(deploy_to, current_dir) } _cset(:release_path) { File.join(releases_path, release_name) }
The current_path
setting is defined with a block that dynamically sets the value based on the deploy_to
setting and the current directory.
Let’s take a look at the deploy recipe that we used in the last episode. At the top of this we set a number of values that are used by Capistrano’s internal deployment tasks.
require "bundler/capistrano" server "178.xxx.xxx.xxx", :web, :app, :db, primary: true set :application, "blog" set :user, "deployer" set :deploy_to, "/home/#{user}/apps/#{application}" set :deploy_via, :remote_cache set :use_sudo, false set :scm, "git" set :repository, "git@github.com:eifion/#{application}.git" set :branch, "master" default_run_options[:pty] = true ssh_options[:forward_agent] = true after "deploy", "deploy:cleanup" # keep only the last 5 releases
We also call after
to make a callback that runs the deploy:cleanup
task after the application is deployed which is a task that Capistrano provides. Further down the file we define a couple of tasks, one to restart the server which overrides the default restart
task and another to do some extra symlinking after the deploy:finalize_update
task is called.
namespace :deploy do task :restart, roles: :app do run "touch #{current_path}/tmp/restart.txt" end task :symlink_config, roles: :app do run "ln -nfs #{shared_path}/config/database.yml #{release_path}/config/database.yml" end after "deploy:finalize_update", "deploy:symlink_config" end
Now with a better understanding of Capistrano’s internal behaviour we can add and override more tasks to configure and customize it to work the way we need to in our deployment scenario.
For more information on Capistrano take a look at the Github wiki3. There’s some good documentation here, especially in the reference section which documents the various methods and variables that we can use. To see the various tasks that are available we can run cap -T
. We can also run cap -e
and supply the name of a task to get full documentation for that task.