Deployments automation with Capistrano

Deployments are a critical phase in any software project. I can still remember the time where I needed to deploy code changes to production using nothing more than FTP to upload all the changed files. Oh boy, the elevated heart rate, logged in on the production server to intervene when something went wrong. From time to time, errors did occur. Forgetting to upload changed files, some discrepancies between development and production. Luckily these days are long over.

Capistrano is a Ruby tool that allows you to deploy web applications to your servers. It has been the de facto standard for deploying Ruby / Rails applications. Capistrano offers a lot of advanced options. I will cover a basic workflow that will allow you to deploy to multiple environments.

In order to install Capistrano, you’ll need Ruby and RubyGems installed on your machine, which I won’t cover here. Try installing rbenv or rvm so you can easily install multiple Ruby versions on your system. If you’re a Ruby developer, you’re probably already using one of these tools.

I’ll explain using Capistrano over SSH using Git. Although you can use Capistrano in combination with FTP, I wouldn’t recommend it. FTP is considered as one of most insecure protocols. Also, every good developer keeps his source code in versioning, so why use FTP.

You’ll need a server that is POSIX-compliant. Don’t forget to setup your SSH keys. This way you still can disallow password logins through SSH and you won’t get prompted for a password when deploying.

If everything is in order, go ahead and install Capistrano through RubyGems. This post will be based on Capistrano 2.X.

$ gem install capistrano

I also recommend you install the capistrano-ext gem that contains an extra set of tools to make your deployments easier:

$ gem install capistrano-ext

Setup your project

To setup Capistrano for your project, navigate to the application’s root directory and run the following command:

$ capify .

This will create a file called Capfile in your project root and a template deployment recipe in config/deploy.rb. Capistrano relies on the fact that you have a config directory into which it will install the deploy.rb file. If there is no config folder present, it will create one for you.

The Capfile loads your recipes and libraries. So whenever you want to extend your your Capistrano setup with extra libraries, you’ll add them here.

The deploy.rb recipe is where all your configuration happens. Here you’ll add all the needed settings for a safe deploy.

Create your recipe

So open up the newly created config/deploy.rb file. The default template will look like this:

set :application, "set your application name here"
set :repository, "set your repository location here"

# set :scm, :git # You can set :scm explicitly or Capistrano will make an intelligent guess based on known version control directory names
# Or: `accurev`, `bzr`, `cvs`, `darcs`, `git`, `mercurial`, `perforce`, `subversion` or `none`

role :web, "your web-server here" # Your HTTP server, Apache/etc
role :app, "your app-server here" # This may be the same as your `Web` server
role :db, "your primary db-server here", primary: true # This is where Rails migrations will run
role :db, "your slave db-server here"

# if you want to clean up old releases on each deploy uncomment this:
# after "deploy:restart", "deploy:cleanup"

# if you're still using the script/reaper helper you will need
# these http://github.com/rails/irs_process_scripts

# If you are using Passenger mod_rails uncomment this:
# namespace :deploy do
#   task :start do ; end
#   task :stop do ; end
#   task :restart, roles: :app, except: { no_release: true } do
#     run "#{try_sudo} touch #{File.join(current_path,'tmp','restart.txt')}"
#   end
# end

First off, lets start by filling in the application name, where ‘my_application’ should contain your application name.

set :application, 'my_application'

Since we are going to deploy from our git repository we’ll set all the appropriate settings:

set :scm, :git # specify your versioning tool
set :repository, 'git@my.gitserver.org:/my_application.git' # specify your repository location
set :scm_passphrase, 'your-passphrase-for-passwordless-logins' # set your passphrase for SSH key
set :user, 'server-user' # set the username you want to use to login into your server
set :deploy_to, '/location/on/server' # set the path where the application needs to be deployed in

Make sure that the user has read & write access to the directory that you which to deploy to.

Also don’t forget to set :deploy_via . Capistrano will by default, create a new clone of your repository on every deploy. That can become slow over time when your repository starts to grow.

set :deploy_via, :remote_cache

This makes Capistrano do a single clone of your repository on your server the first time and do a git pull on every future deploy. If you deploy often, you’ll notice that this speeds up your deployments.
Another useful property is the :use_sudo property. I always set it to false:

set :use_sudo, false

This way, Capistrano will, almost, never sudo to root during deployments. I like to keep my projects as isolated as possible. So none of my project users should need root permissions to perform deployments in their own project directories.

Don’t forget setting the :keep_releases. With this property, you’ll be able to define how many releases should be kept on the server.

set :keep_releases, 2

If you want the cleanup to happen automatically, you’ll need to set the hook accordingly so the cleanup task gets called ( see the section Custom tasks and hooks ).

Roles

You might have noticed the roles properties. Roles define specific servers that could be in use by your application. It allows you to write specific Capistrano tasks that only apply to certain servers. But this only applies to multi-server deployments.

In this example, we have only one server being assigned all three roles (app, web and db) by using the server property:

server 'example.org', :app, :web, :db, primary: true

Otherwise, you can specify every role independently. For the :db role, migrations will be run on the server that is set as primary.

Multistage

Capistrano-ext provides a multistage extension which allows to use a different deployment strategy for different scenarios. Most common scenario might be 2 servers: one for production, ready for the end-users and one for staging, where you can test new features without the risk of affecting the end-users when something is still unstable.

In order to use the multistage extension, you need to require the multistage library in your Capfile:

require 'capistrano/ext/multistage'

Also add the following lines to the deploy.rb file:

set :stages, %w(production staging) # define possible stages
set :default_stage, 'staging' # define default stage

Now, in order to configure each stage, you must create a proper configuration file per stage. These configuration files reside by default in the config/deploy/ directory where each configuration file has the name of the associated stage. So in this example you’ll have two configuration files, production.rb and staging.rb.

From this point on, you can overwrite any Capistrano property you want, depending on the scenario.

# production.rb
server 'production.example.org', :app, :web, :db, primary: true
# staging.rb
server 'staging.example.org', :app, :web, :db, primary: true

Custom tasks & hooks

You can create a Capistrano task the same way as you create a rake task. Lets have a look at the custom task to restart your application when using passenger:

namespace :deploy do
  task :start do ; end
  task :stop do ; end
  task :restart, roles: :app, except: { no_release: true } do
    run "#{try_sudo} touch #{File.join(current_path,'tmp','restart.txt')}"
  end
end

So here we wrap our task in the :deploy namespace. This is something to remind, always wrap your tasks in a namespace. It doesn’t have to be the :deploy namespace per se, but in this case, the restart task is always called when you run a deploy.

Besides giving your a task name, your also specify on which roles the task applies. When using a separate database server and you wish to apply a certain action on the database, you will assign the :db role to the task.

You can pass the :no_release attribute to the role definition ( commonly done for the web role). This indicates that the code should not be run on servers in that role.

So for instance, if we define our roles as following:

role :app, 'app.example.org'
role :web, 'web.example.org', no_release: true
role :db, 'db.example.org', primary: true

The restart task will not be run on the web server. Keep in mind that the roles and except usage is optional.

Now to hook a task in your deployment configuration, you simply need to specify in what order your task needs to be run. This is done by configuring before / after what task, your task needs to be run. For example, when you wish to keep 3 releases, you can configure Capistrano that it will run the cleanup task after everything got deployed:

after('deploy', 'deploy:cleanup')

Using Capistrano

Now when everything is set to order, the first thing you’ll need to do is setup the correct directory structure. This can be done by running the following command:

$ cap <stage> deploy:setup

Capistrano will now create the following structure:

<deploy_to>/releases # your code will reside in this folder
<deploy_to>/current -> releases/ # this is a symlink to the current release 
<deploy_to>/shared -> # holds all the files that need to be shared over releases ( eg. log files)

The last step before we can do an actual deployment with Capistrano is to make sure that everything is set up correctly on the server from the setup command. There’s a simple way to verify, with the command:

$ cap <stage> deploy:check

This command will check your local environment and your server and locate likely problems. If you see any error messages, fix them and run the command again. Once you can run cap deploy:check without errors, you can proceed!

$ cap <stage> deploy

When you run the deploy command, Capistrano will run by default the following commands:

deploy:update_code  # this will pull your latest code from versioning to the releases folder
deploy:symlink      # this will set the current symlink to point to the latest release
deploy:restart      # this will restart your application ( this needs to be customized )

Now, it may happen that something breaks in your new deployment. No need to panic, simply rollback to the previous version:

$ cap <stage> deploy:rollback

The only thing the rollback task does is reset the current symlink to the previous release.