Capified: Painless Deployment for Free

You have built an awesome application. You need to get it out there. Let’s face it, deployments should be easy, repeatable and if it doesn’t work out, you should be able to roll back to the last known good state.

If you are using manual Git workflow (other SCMs are available) or, gawd forbid FTP, then I can hear you scream “Preach it!”. I have been there, watching all those files uploading via FileZilla, migrating the database, restarting the server(s). If you have ever automated your deployments, even just once, you will have been spoiled. Anything else is pain.

You most likely will have heard of Capistrano. It’s an opinionated deployment tool written in Ruby. Capistrano offers user defined tasks, via an elegant DSL, for making the deployment of your latest web application a snap.

Today we will walk over a few basics, examine possible web server setups, and look at setting up some custom deployment tasks.

Opinions Matter

Remember I said Capistrano is opinionated? These opinions come in the form of a few assumptions Capistrano makes about your application:

  • You will use source control
  • You will have a “config” directory at the top level of your application
  • You will use SSH to access the box we are deploying to

Your Server Setup

We need to talk about the setup of your server box. Services such as Heroku and EngineYard take away all the pain of setting up MySQL, Apache and so on. However, if we look around the web there is a plethora of cheap VPS which will meet your needs just as well. Sure, we will have to do some initial work to get the server setup, but that is a one time deal and we can automate it with a little know how.

My VPS uses users as hosting accounts. If I have an application named “capdemo” I will also have a user on the box named “capdemo”, with a home directory acting as their piece of the hosting pie.

I also use an Apache server, mainly because I am very familiar with it. NGINX is an alternative which gets a good write up. For now, I’m sticking with Apache. Both servers play nice with the next assumption of this article, which is: we will use Passenger.

Passenger gives us a mainstream deployment process. There is no special server configuration, or port management. A new release is just a case of uploading the files and restarting the application server (Mongrel, Unicorn etc.).

Just a short note on restarting these servers. Passenger looks for a file called tmp/restart.txt in your application direcotry to tell it when to restart the application server. A manual restart would be touch tmp/restart.txt.

Cooking with Capistrano

These days, I get really hungry talking devops. When I build a Capistrano script, I am developing a recipe telling Capistrano how I would my web application prepared (medium rare, maybe?). There is no splashing in extra Sriracha for heat. The recipe is followed to the letter. If it can’t complete the deployment, Capistrano lets you know and cleans up the dishes.

Capistrano works great with Rails applications, but can be used with pretty much any application. The application doesn’t even have to be Ruby-based. In this case, however, we will use a Rails application to get cooking.

As usual add gem capistrano to your Gemfile and run bundle install. Now, we can “capify” our project by running capify .. This creates a Capfile and a deploy script in the config directory of our application.

It is within the config/deploy.rb file where we will create our deployment recipe. Looking at the deploy.rb file, we see Capistrano has been nice enough to get us started.

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

set :scm, :subversion

# 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'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

Your generated deploy.rb file should look like the above (at the time of writing the latest stable version of Capustrano is 2.9.0). This gives us a bit of a head start for our recipe.

So lets change that to something more homemade. First, we need to setup some SSH configuration, information about the application, details of where it is to be deployed to and some SCM details.

ssh_options[:forward_agent] = true

require 'json'
set :user_details, JSON.parse(IO.read('dna.json'))

set :domain, "capdemo.bangline.co.uk"

set :application, domain
set :user, user_details['name']
set :password, user_details['password']
set :deploy_to, "/home/#{user}"
set :use_sudo, false

set :repository, "git@github.com:bangline/capdemo.git"
set :scm, :git
set :branch, "master"

set :deploy_via, :remote_cache

role :app, domain
role :web, domain
role :db, domain, :primary => true

# 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

The ssh_options[:forward_agent] ensures we use the keys on our local machine rather than those on the server. I use this as I do not usually place keys on the server to access GitHub, but it is completely plausible to do so (delete this line if you are).

I then parse a file named dna.json for user credentials. Not only can I omit this sensitive file from a public git repo, but it can be used to make the recipe more reusable. For instance we could also setup all the SCM details in the dna file. The contents of the dna.json file look like:

{
  "name":"chuck_norris",
  "password":"dont_need_one_as_the_server_knows_and_fears_me"
}

The next few lines explain themselves pretty well. We setup the application name, the user credentials on the server, our git configuration and,finally, where the application is to be deployed.

I should point out here I am not using keys for SSH access. If the user password is set then Capistrano will use this throughout the deployment. It’s fine for this scenario, but if we were deploying across multiple servers Capistrano would assume the password was the same for all servers. In other words, for multi-server deployment use SSH keys. It is not the most secure method, but pretty flexible. Just make sure your password looks like a cat took a nap on your keyboard.

I have also set the :deploy_via to :remote_cache. This creates a git repo on the server itself, preventing a full clone of the application on every deployment.

The role definitions describe the layers of our application. The :app layer is what we are most used to in development, the :web layer is where all the requests go and :db is where we want to run migrations. This style of configuration can look silly as we are only using a single box (the server keyword addresses this) but if we ever need to scale and separate the database and so on, then this style is more maintainable.

It is possible to do a couple of checks at this point. If you run the cap -T command in your terminal you will see what tasks Capistrano already knows about. At this time, we want to setup the applications directory and check the permissions are correct.

cap deploy:setup
cap deploy:check

Capistrano Layout

Before going on any further, we can examine what layout to expect on our server. If we SSH to the box and check the application path we should see:

  • current
  • releases
  • shared

The current is simply a symlink to the latest in the releases folder. Having this constant we can then set our apache vhost config file to the following

<VirtualHost *:80>

# Admin email, Server Name (domain name) and any aliases
  ServerName capdemo.bangline.co.uk

# Index file and Document Root (where the public files are located)
  DocumentRoot /home/capdemo/current/public
  <Directory /home/capdemo/current/public>
        Options -MultiViews
        AllowOverride All
  </Directory>

</VirtualHost>

The releases directory holds all the releases we do using Capistrano. The current symlink points to the latest directory in here. The releases directory will hold all previous releases. We can limit the number of releases to keep in our deployment recipe with set :keep_releases, 5 or use the Capistrano task cap deploy:cleanup.

The shared directory persists across deployments, so put items like user uploaded assets or sqlite databases in this directory.

Writing a Recipe

So far all we have done is check everything is in place for our first deployment. The output of cap deploy:check should have told us everything is looking good. If not, you probably have to check that the permissions for the user are correct. Remember I told you to clear out the deploy.rb file? Well, the truth is as using Capistrano with Passenger is so easy, it’s almost expected. We left some commented out code in the deploy.rb and we need it now.

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

These are the first deployment tasks we are adding. We override the defaultstart,stopandrestarttasks to be specific for our setup. Therestarttask can be called usingcap deploy:restartand you can see it touches thetmp/restart.txt` file. What is more important is the disection of a task.

We have a namespace, deploy and some sub tasks. When we call cap deploy:restart the only task executed is restart. Calling cap deploy in the terminal will run all the tasks we see under the namespace and a few others we cannot see. Under the hood the cap deploy task has a set of stored/default tasks. The details of which have been nicely illustrated here. The bits we are interested in, in order which they are called, are:

  • deploy:update_code - Pulls the code from the git repository
  • deploy:symlink - Links the most recent release to current
  • deploy:restart - We have overridden this to just touch the restart.txt file

Another consideration is we are developing a Rails 3.1 application. We want to use bundler to manage our gem dependancies (why would we wnat anything else?) Luckily, bundler has a deployment task for capistrano.

require "bundler/capistrano"

Simply adding this line to our deploy.rb will bundle all our gems on deployment. It also does this task in a smart way. All the gems are packaged into the shared/bundle directory. This is the modern day equvalent to freezing our gems. With that in place we are nearly ready to deploy.

Since, this is going to be our first deployment, we need to perform a couple of extra tasks from the command line. First, we need to migrate the database. Without breaching any copyright 'There is a task for that'. So let's get some code on the server using cap deploy:update_code then run cap deploy:migrate. At this point we have code on the server, a database with the latest schema and the our gem dependancies have been fulfilled.

Throwing in Extra Ingredients

As I mentioned before, we will be deploying a Rails 3.1 application. Along with the asset pipeline came the ability to pre compile our assets. This gives us a perfect excuse to create our own deployment task.

namespace :assets do
  task :compile, :roles => :web, :except => { :no_release => true } do
    run "cd #{current_path}; rm -rf public/assets/*"
    run "cd #{current_path}; bundle exec rake assets:precompile RAILS_ENV=production"
  end
end

before "deploy:restart", "assets:compile"

What this task does is, simply, delete any existing assets and run the rake command to compile them. The more interesting part is the hook I have placed at the bottom. You probably know what it does already thanks to the eloquent DSL Capistrano has, but basically it hooks in to the deploy task and runs our assets:compile task before restarting the applicatiion server. Also, by splitting it out into it's own namespace we can run it from the command line in isolation cap assets:compile.

There is one last thing we need to do before deploying. Remember when we talked about the shared directory being a good place to keep sqlite databases? Well the current config in our database.yml file is still using db/production.sqlite3. The simplist fix for this is to change this to /home/capdemo/shared/production.sqlite3. We commit that to our GitHub repo and run cap deploy:update_code and cap deploy:migrate. Now that we have the database persisting across our deploys, we can actually deploy the app, simply:

cap deploy

If you follow the output of the deployment you will see the compile task being executed before the restart task. Admittedly, the output does look incorrect, but if you take a bit of time to read it you will see it's doing what we expect.

* executing `deploy:restart'
triggering before callbacks for `deploy:restart'
* executing `assets:compile'

Writing your own tasks is a great way to learn what's going on under the hood with Capistrano. A couple of things to rememeber about tasks is they are written in plain old Ruby, so you have all the usual idioms available to you. The other is Capistrano gives you a good set of configuration variables such as current_path, shared_path. A full list has been compiled by a chap Eric Davis.

To cement those points we will look at building just one more custom task. I enjoy looking at my deployment history, so using our knowledge of the shared directory and the capistrano deployment process, we can build a log file of deployments.

task :log_deploy do
  date = DateTime.now
  run "cd #{shared_path}; touch REVISION_HISTORY"
  run "cd #{shared_path};( echo '#{date.strftime("%m/%d/%Y - %I:%M%p")} : Vesrion #{latest_revision[0..6]} was deployed.' ; cat REVISION_HISTORY) > rev<em>tmp &amp;&amp; mv rev_tmp REVISION_HISTORY"
end

task :history do
  run "tail #{shared_path}/REVISION_HISTORY" do | ch, stream, out |
    puts out
  end
end
after "deploy", "deploy:log_deploy"

From here on in it's plain sailing for deployments. We just use cap deploy or cap deploy:migrations depending on if we need to update the db schema for a release. If we want to look at the deployment history, we just call cap deploy:history to get an output of the date, time and version of all our deployments.

Savoring Our Deployment

Hopefully now you have an appetite for rolling your own deployment with Capistrano. I tried to make this walkthrough as detailed as possible. I was reluctant at first with Capistrano as I wasn't all that confident with my shell skills, and I was scared to lose human control. But that was just silly of me.

Not only did I save myself the pain of manual deployments, but I also got all the extra goodies Capistrano gives you, such as roll back capabilities, automatic disabled page and so on. There are still a great deal of features that come standard with Capistrano, the details of which can be found in the documentation. There is also plenty of recipes out there for you to borrow and build on.

Yes, we do have to invest a little more time developing our deployment than using something like Heroku. Howerver, after writing a few recipes, it will come as second nature and, maybe, a cheap VPS will start to look more attractive. The source is available on GitHub.

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

No Reader comments

Comments on this post are closed.