I’ve recently had the opportunity to build an application for a startup using Rails. While this certainly wasn’t my first experience with Rails, it would my first exposure to administering a Rails application in a soon to be production environment. Since I am inherently lazy (like most techies), I wanted to make sure I had an automated process for pushing out changes and re-deploying the application. After all, startups are notorious for rapid fire modifications based upon early feedback loops. This seemed like a great opportunity to try out Capistrano.
Capistrano is a build automation tool, similar to Ant. However, while Ant doesn’t make many assumptions about what (or how) your building and deploying, Capistrano makes a lot of assumptions (sound familiar?). As a result, Capistrano can quickly become the centerpiece of your deployment process. Capistrano leverages SSH to execute remote shell commands on one or more of your deployment servers, checking out code from your SCM (usually SVN or git), managing a directory structure of releases and creating symbolic links to your current release. It will even help you remotely start and stop your app and erect a maintenance page! All of this is accomplished via Capistrano Tasks and Recipes that are scoped by a namespace to avoid conflicts. Of course, you can easily create your own Recipes, but I didn’t have the need in my particular situation. The out of the box deploy script generated by Capistrano for me was more than adequate, although I did make some tweaks (see below) based on the fact that I was using Passenger (i.e. mod_rails) in my Apache configuration on the deployment server. Here is what I had to do to get my new application up and running with Capistrano:
Installation
Installation was a snap. Capistrano is ONLY installed on your development machine. It is a simple gem install:
$ sudo gem install capistrano
...(output omitted)...
Successfully installed capistrano-2.5.5
Setting up my project
Next I navigated to my rails project root and executed the following command:
$ capify .
[add] writing './Capfile'
[add] writing './config/deploy.rb'
[done] capified!
I’ve been capified! Well that wasn’t too painful.
Tweaking for environment
After capification (?) I updated the ./config/deploy.rb file generated by Capistrano. Mine looked similar to this (some values changed to protect the innocent). Note that I also put some nice comment dividers to help organize the information. They are not required:
#############################################################
# Application
#############################################################
set :application, "myrailsapp"
set :deploy_to, "/var/www/#{application}"
set :rails_env, "production"
#############################################################
# Settings
#############################################################
default_run_options[:pty] = true
set :use_sudo, true
#############################################################
# Servers
#############################################################
set :user, "some_user_name"
set :domain, "yourdomain.com"
server domain, :app, :web
role :db, domain, :primary => true
#############################################################
# Subversion
#############################################################
set :repository, "svn+ssh://www.yourdomain.com/repos/myrailsapp/trunk"
set :svn_username, "svn_user_name"
set :svn_password, "svn_password"
set :checkout, "export"
#############################################################
# Passenger
#############################################################
namespace :deploy do
task :start, :roles => :app do
run "touch #{current_release}/tmp/restart.txt"
end
task :stop, :roles => :app do
# Do nothing.
end
desc "Restart Application"
task :restart, :roles => :app do
run "touch #{current_release}/tmp/restart.txt"
end
end
Everything is pretty self explanatory. However, at the risk of pointing out the obvious… In the Application related properties, set the name of the rails app. This name can be anything but it will be used in the path to the deployment location in the next line so you may want to put some thought into this. I set the rails environment to ‘production’. I didn’t mess with the Settings related properties as I knew I would need to execute commands on the remote server as sudo. The server related properties include the user name that will attempt the SSH session. Domain could be something that resolves via DNS or an IP address, of course. I also needed to update the SVN properties with the path to my repository and my username/password.
For the most part, everything up to this point was out-of-the-box. All I needed to do was adjust some user names, passwords, and server paths. The last section (Recipe?) I added to accommodate the fact that I was using Passenger (i.e. mod_rails) on my Apache-based server as the glue between Apache and Ruby/Rails. This is really simple. The namespace used by Capistrano is ‘deploy’. All I am doing is overriding the default implementation of the :start, :stop, and :restart tasks. In the case of Passenger, all that is necessary is to “touch” a special file in the {RAILS_ROOT}/tmp directory called restart.txt. Passenger monitors this file and will reload when it sees that it has been modified.
That’s just about it. Well, almost. You’ll want to perform the task ‘cap deploy:setup’ from your RAILS_ROOT directory (again, on your development machine) first. This will set up a small directory structure under your target deployment location (adding directories ‘releases’, and ‘shared’). I had a small problem with permissions on the server so I had to manually SSH to update the file permissions in my target directory (i.e. ‘/var/www/myrailsapp’ directory to allow for writing. That was the only glitch for me and it only had to be done once.
Deployment
Next, I executed the following commands:
$ cap deploy:cold
...(output ommitted) [be prepared to respond to the SSH password and sudo password prompt]...
This launched a remote SSH session, checked out the latest version from SVN, built a new timestamp directory under the ‘releases’ subdirectory and created a symbolic link called ‘current’ to the current release. This way, my Document Root setting in my Apache configuration could simply refer to the ‘/var/www/myrailsapp/current/public’ directory (don’t forget that you need to include the public directory in the Apache config). Future releases could be done with:
$ cap deploy
…from the RAILS_ROOT directory of the development machine and voila. It’s automation bliss. There are loads of out of the box tasks (execute ‘cap –tasks’ to see the full list) to help you in the deployment and management process. A couple that I have been using a lot are ‘cap deploy:web:disable’ and ‘cap deploy:web:enable’. This will have Capistrano put up a maintenance page when you ‘disable’ your website for maintenance and remove it when it is back online ‘enabled’. However, be sure to update your Apache mod_rewrite rules to enable this. Here is what I added to my Apache virtual host configuration:
RewriteCond %{REQUEST_URI} !\.(css|jpg|png)$
RewriteCond %{DOCUMENT_ROOT}/system/maintenance.html -f
RewriteCond %{SCRIPT_FILENAME} !maintenance.html
RewriteRule ^.*$ /system/maintenance.html [L]
My experience was very positive with Capistrano thanks to the power of opinionated software. Your mileage may vary.
