Capistrano Deploy with Git and Passenger

One of the great things about Rails and its community is that they are very lazy. Lazy in the good way of not wanting to do boring, repetitive, error prone things manually. They metaprogram and they automate. A great example of this is Capistrano. Capistrano allows you to deploy Rails applications with ease. The normal scenario is baked into Capistrano as a deployment convention and then you can customize it if you need to.

My Story

I’ve recently redeployed a couple of Ruby on Rails sites using Passenger (mod_rails). Passenger is an Apache module that really simplifies the deployment of small-scale Rails applications. Once Passenger is installed and your Rails application is set up as a virtual directory, it just works. Passenger auto-detects the fact that the directory is a Rails application and runs it for you. No need for a mongrel cluster or manually configuring load balancing.

I’m also using Git as my version control system on small, personal projects because it’s easy to use and I can work on multiple laptops and commit locally and worry about pushing to a central location when I have a network connection.

Seeing those things, I wanted to make them all work with Capistrano so that I could continue being lazy. To do this, I’m using Capistrano v2.4. It has Git support built in that works (some previous versions had support for Git, but seemed to have a lot of trouble).

Git Setup

By convention, Capistrano uses Subversion. So, I need to change my configuration to use git. The set :scm, :git does this. The repository information sets up where my git repository lives. In this case, I’m just using a bare git repository accessing it over SSH. You can also access your repository using the git and http protocols if you have that setup. The branch just says to deploy off of the master branch.

That’s pretty much it – nice and easy.

set :scm, :git
set :repository, "geoff@zorched.net:myapp.git"
set :branch, "master"
set :deploy_via, :remote_cache

Passenger (mod_rails) Setup

The only thing that comes into play with Passenger is restarting the Rails application after a deployment is done. Passenger has an easy way to do this which is just to create a file called restart.txt in the Rails tmp directory. When it sees that, the Rails application process will be recycled automatically.

Doing this requires just a bit of Capistrano customization. We need to override the deploy:restart task and have it run a small shell script for us. In this case we are running run “touch #{current_path}/tmp/restart.txt” to accomplish this task.

namespace :deploy do
  desc "Restarting mod_rails with restart.txt"
  task :restart, :roles => :app, :except => { :no_release => true } do
    run "touch #{current_path}/tmp/restart.txt"
  end

We can also override the start and stop tasks because those don’t really do anything in the mod_rails scenario like they would with mongrel or other deployments.

  [:start, :stop].each do |t|
    desc "#{t} task is a no-op with mod_rails"
    task t, :roles => :app do ; end
  end
end

The Whole Thing

Putting everything together in my deploy.rb looks like the following:

set :application, "enotify"
 
# If you aren't deploying to /u/apps/#{application} on the target
# servers (which is the default), you can specify the actual location
# via the :deploy_to variable:
set :deploy_to, "/var/www/myapp"
 
# If you aren't using Subversion to manage your source code, specify
# your SCM below:
set :scm, :git
set :repository, "geoff@zorched.net:myapp.git"
set :branch, "master"
set :deploy_via, :remote_cache
 
set :user, 'geoff'
set :ssh_options, { :forward_agent => true }
 
role :app, "zorched.net"
role :web, "zorched.net"
role :db,  "zorched.net", :primary => true
 
namespace :deploy do
  desc "Restarting mod_rails with restart.txt"
  task :restart, :roles => :app, :except => { :no_release => true } do
    run "touch #{current_path}/tmp/restart.txt"
  end
 
  [:start, :stop].each do |t|
    desc "#{t} task is a no-op with mod_rails"
    task t, :roles => :app do ; end
  end
end

Start a New Branch on your Remote Git Repository

Git is a distributed version control system so it allows you to create branches locally and commit against them. It also supports a more centralized repository model. When using a centralized repository you can push changes to it so that others can pull them more easily. I have a tendency to work on multiple computers. Because of this, I like to use a centralized repository to track the branches as I work on them. That way no matter what machine I’m on, I can still get at my branches.

The Workflow

My workflow is generally something like this:

  1. Create a remote branch
  2. Create a local branch that tracks it
  3. Work, Test, Commit (repeat) – this is all local
  4. Push (pushes commits to the remote repository)

Git commands can be a bit esoteric at times and I can’t always seem to remember how to create a remote git branch and then start working on new code. There also seems to be multiple ways of doing it. I’m documenting the way that seem to work for me so that I can remember it. Maybe it will help someone else too.

Creating a Remote Branch

  1. Create the remote branch

    git push origin origin:refs/heads/new_feature_name

  2. Make sure everything is up-to-date

    git fetch origin

  3. Then you can see that the branch is created.

    git branch -r

This should show ‘origin/new_feature_name’

  1. Start tracking the new branch
    git checkout --track -b new_feature_name origin/new_feature_name

This means that when you do pulls that it will get the latest from that branch as well.

  1. Make sure everything is up-to-date
    git pull

Cleaning up Mistakes

If you make a mistake you can always delete the remote branch

git push origin :heads/new_feature_name

(Ok Git’ers – that has to be the least intuitive command ever.)

Use the Branch from Another Location

When you get to another computer or clone the git repository to a new computer, then you just need to start tracking the new branch again.

git branch -r

to show all the remote branches

git checkout --track -b new_branch origin/new_feature_name

to start tracking the new branch

Automate it A Bit

That’s a pretty easy thing to automate with a small shell script luckily

</p>
 
<h1>!/bin/sh</h1>
 
<h1>git-create-branch <branch_name></h1>
 
<p>if [ $# -ne 1 ]; then
         echo 1>&amp;2 Usage: $0 branch_name
         exit 127
fi</p>
 
<p>set branch_name = $1
git push origin origin:refs/heads/${branch_name}
git fetch origin
git checkout --track -b ${branch_name} origin/${branch_name}
git pull

For further help, you might want to check out: