Like any other build process, deploying Rails applications should be a quick and easy task. You should be able to deploy everything to your server with just one command. There are several tools available for this task. One of them is Capistrano, which is probably the oldest and by far the most popular deployment tool in the Rails community. Unfortunately the documentation is still not great and a lot of very handy features are unknown to many. Only digging through the API documentation and the source code reveals them.
Variables
A lot of tasks for deploying Rails applications are the same for almost every application. They only differ by minor aspects such as the path to deploy to, or the branch to checkout the code from. Capistrano allows setting these aspects using variables. Setting a variable is as simple as calling set :name, 'value'
. Accessing a previously defined variable is even easier by just using it as a ruby method.
set :name, 'value'
puts name # Prints 'value'
However there are some aspects of Capistrano variables that make them really useful.
Accessing variables that might not exist
In some cases you don’t want to set every single variable to configure certain aspects of a task. But this comes with a problem. Accessing a variable that has not been defined before, will make Capistrano yell at you and crash with a NameError
.
There are two ways to handle this. One of them is to define all variables with sane defaults and overwrite them as needed afterwards. The other one, and by far my preferred one, is to use Capistrano’s fetch
method. It allows you to access a variable and additionally provide a default that will be returned when the variable is not defined, yet. This allows you to write flexible tasks without having to define every single variable upfront.
fetch(:some_setting, false)
Setting variables via the command line
Another very useful feature is the possibility to set variables via the command line. Appending a -S variable_name=value
option to the command line will set the given variable to the given value. This comes with a minor caveat, though. Any call of set
with the same variable name will overwrite the value set by the command line option. Luckily Capistrano’s exists?
method allows you to check for the existence of a variable. This allows you to only set a variable if it doesn’t exist yet. You might change your set :branch
call to something like this:
set(:branch, 'staging') unless exists?(:branch)
This allows you to deploy any branch to your staging servers without fiddling with the configuration files, just by appending -S branch=other-branch
to your cap deploy
call:
cap deploy -S branch=awesome-new-feature
Combining the ability to set variables via the command line with the previously explained fetch
method is also useful to toggle certain aspects of your deployment.
namespace :translations do
task :update do
if fetch(:with_translations, true)
# Download current translations from your translation service provider
end
end
end
Reading passwords from STDIN
Variables are great to store your configuration options. There’s just one exception: Passwords. You probably don’t want to have your production database password checked into your repository. This is where Capistrano’s command line interface tools come in handy. It allows you, among several other options, to read passwords from STDIN. It even makes sure your terminal won’t print it while you’re typing.
Capistrano::CLI.password_prompt('Enter database password: ')
Task descriptions
As you probably know, it’s possible to list all available tasks using cap -T
. This will print a list of all available commands. Every task with a description will show up in this list. Adding a description is easy. Just prepend the task definition with a desc
call, passing it a string.
Adding extended description
Each defined task may also have an extended description. To show it, simply execute cap -e task name
. Adding an extended description works just like adding the short description explained above. Just like git commit
, Capistrano will use the first paragraph as short description and every following paragraph as extended description. Ruby’s heredoc literal is handy when defining extended descriptions:
desc <<-EOS
This is a short description that is visible in the `cap -T` list
This the extended description that is visible when executing `cap -e task name`
EOS
Hiding internal tasks
Some tasks you’ll define for your deployment process are not intended to be executed manually and therefore shouldn’t show up when using cap -T
. The simplest way to prevent this, is to not give them any description. However, in some cases, a description would be useful for later reference and documentation. By prepending the description with [internal]
you can hide the task from the cap -T
list. It will then only be visible when executing cap -vT
, which will list absolutely all defined tasks.
desc '[internal] This task does something useful but you should not invoke it manually'
Executing commands
Running commands on the remote servers is easy. Capistrano’s run
method will execute the given command on every server the current task applies to. There are two more methods related to command execution that are very useful, though.
Running local commands
One of them is run_locally
. It behaves just like run
, except it will run the command on your machine. While it is of course possible to use Ruby’s various methods for command execution, run_locally
will ensure both the command and it’s output will show up cleanly aligned with all other output of the Capistrano run.
run_locally 'bundle exec rake deploy:prepare'
Streaming output from remote servers
The other very useful method is stream
. It will execute the given command on every server the task applies to, but unlike the run
method it will stream the command’s output to your console. This allows you, for example, to peek into your log files in realtime. Combined with grep
and Rails’ ActiveSupport::TaggedLogging
this helps a lot when debugging an issue that only shows up on your servers.
stream "tail -f #{current_path}/log/#{rails_env}.log"
Handling failing commands
When executing a remote command that fails, Capistrano will abort the deployment process. This makes sense in most cases, but wouldn’t it be nice to have a little more control over the behavior? Let’s say you want to check for the existence of a file on the remote server. One easy way to do this is the test
command. It’s exit code will be 0
for success or 1
for failure. Unfortunately this means one of the cases will cause the entire deployment process to fail. To prevent this, all you have to do is to rescue Capistrano::CommandError
. A practical example for this technique is this task, that checks if the server is in maintenance mode before running migrations.
namespace :deploy do
namespace :web do
desc '[internal] Ensures that web is currently disabled'
task :ensure do
begin
run "test -f #{shared_path}/system/maintenance.html"
rescue Capistrano::CommandError
unless fetch(:force, false)
logger.important 'You should disable the website using deploy:web:disable before doing this'
exit
end
end
end
end
end
before 'deploy:migrations', 'deploy:web:ensure'
Conclusion
This is – of course – only a small subset of the useful features Capistrano provides. I hope I showed you one or two you haven’t known before. I encourage you to read through Capistrano’s source code and API documentation to find more of these hidden features.