Like most people I use capistrano to deploy my rails applications, at work we host with the excellent railsmachine and they have really helped simplify the process of deploying to their servers using the railsmachine gem, which builds on top of the excellent functionality of capistrano.

The way these tools work by default they only allow you to deploy one instance of your application, which is fine, that’s all they’re are intended to do. But in the client facing world you’re probably going to want to have at least two versions of the same application at different stages of development running on the same server.

A live forward facing version (my-app.com) and a staging version (staging.my-app.com) for client approval / production testing (not code testing, that should stay on your development box) / progressive reviews etc.

So how do we achieve that? The idea is to determine what context you are deploying your application in, and use the fact the capistrano tasks can be chained together to set everything up ready for deploying a revision of your application to a targeted url, independent of other deployments.

Step 1

The first thing to do is to require the railsmachine recipes and setup some variables, as you will see later some of these variables are redundant, and the code has a fair bit of duplication, but this is a work in progress and I hope to clean it up when I have some more spare time.

require 'railsmachine/recipes'

set :application, "my-app"
set :deploy_to, "/var/www/apps/#{application}"
set :main_domain, "my-app.com"

set :user, "deploy"
set :repository, "svn+ssh://#{user}@my-svn-server.com/var/svn/#{main_domain}/trunk"
set :rails_env, "production"

role :scm, "my-svn-server.com"

You’ll notice that I’m keeping my my project on separate svn server, by default the railsmachine gem will create an svn repo for you as part of the project tree, this is really useful at first, but when you’re deploying multiple instances of the same application you’re going to want your svn repo independent.

Step 2

Now comes the clever bit, creating the tasks which will be called before our regular cap commands, like so $ cap -a staging deploy (which would deploy the current revision of your application to the staging server)

desc "Set staging instance variables"
task :staging do
    set :application, "my-app-staging"
    set :domain, "staging.my-app.com"
    set :apache_proxy_port, 8010
    set :database_yml, "database-staging.yml"
    set :deploy_to, "/var/www/apps/#{application}"

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

desc "Set production instance variables"
task :production do
    set :application, "my-app-production"
    set :domain, "my-app.com"
    set :apache_proxy_port, 8000
    set :database_yml, "database-production.yml"
    set :deploy_to, "/var/www/apps/#{application}"

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

What this does is set all of the variables which any command you’ll call after staging or production will need, it’s pretty ugly at the moment and I hope to clean it up and make it a lot smarter, but for the moment it works, and in it’s ugliness it does make it clear what it’s doing.

Step 3

Next we overwrite the railsmachine setup_scm task and one of the tasks it calls, we’re doing this because we are using a custom location for our repository and not the one which the railsmachine gem tries to create for us.

In my particular situation I have an external server used for svn, but the repository could just as easily be on the same server as your application just so long as the folder you create the repo in is in the deploy group. Also in my situation I am creating a new repo for each project I deploy, if you already have a repo that you deploy all projects to you could easily just remove the setup_svn_repo task call from the setup_scm task.

Enough talk, on with the code:

desc "Setup svn repository"
task :setup_svn_repo, :roles => :scm do
  dir = "/var/svn/#{main_domain}"
  run "mkdir -p #{dir}"
  run "chmod 770 #{dir}"
  run "svnadmin create #{dir}"
end

desc "Setup source control server."
task :setup_scm, :roles => :scm  do
  setup_svn_repo
  import_svn
end

Step 4

Because different instances of our application are going to need different databases we have to overwrite the method which read’s in the database configuration when setting up the database for the first time. The way it works at the moment we also need to remove the database.yml file from svn control, and recreate it from the correct file after every code update.

task :clean_db_yml do
  puts "removing database.yml from svn"
  system "svn remove config/database.yml"
  puts "ignoring database.yml"
  system "svn propset svn:ignore 'database.yml' config/"
  system "svn update config/"
  puts "committing changes"
  system "svn commit -m 'Removed and ignored database.yml file'"
end

def read_config
  db_config = YAML.load_file("config/#{database_yml}")
  set :db_user, db_config[rails_env]["username"]
  set :db_password, db_config[rails_env]["password"]
  set :db_name, db_config[rails_env]["database"]
end

task :after_update_code, :roles => :app do
   correct_db_yml
end

task :correct_db_yml do
  put(File.read("config/#{database_yml}"), "#{release_path}/config/database.yml", :mode => 0444)
end

Thats all there is to it, I run the following commands to set up an instance of my app:

cap -a staging setup
cap -a staging setup_scm
cap -a staging setup_servers
../[new directory]
cap -a staging clean_db_yml
cap -a staging cold_deploy
cap -a staging restart_web

cap -a staging deploy
cap -a staging deploy_with_migrations

The End

There you have it, it works, it’s not the most elegant solution and definitely something I want to work further on. The last section with database.yml stuff is by far the ugliest, but it is a work in progress, and I plan to make further amendments to it.

3 comments

add your own

  1. # on 10 Mar 07 @ 1:31 bryanl said this:

    I’ve done something similar. Jamis Buck pointed me to this method, and I’ve been using it ever since: http://groups.google.com/group/capistrano/msg/ce9b54912a705aa7

  2. # on 10 Mar 07 @ 4:23 craig mackenzie said this:

    i think i’m going to investigate Jamis’s method. i think there may be a problem with my way because when i checked my svn server it would appear as though the application is being checked out to the /var/www/apps directory there also. it’s something i really need to get nailed.

  3. # on 03 Mar 09 @ 9:49 Alexwebmaster said this:

    Hello webmaster
    I would like to share with you a link to your site
    write me here preonrelt@mail.ru

your turn

your private data is never published or shared. required fields are marked *

metal&gin ?

metal & gin is the personal blog of craig t mackenzie, a scary boy with delusions of grandeur, and a panache for geek-chic. craig lives in the UK and writes code for avenue a | razorfish. you can find out more about him in the about section.

this blog mostly focuses on matters of geekery as well as any random musing that pops into craig's head. this is also a place for meta-data about craig to be collated.