« No Really, Perforce Does Suck | Main | Zip and Preserve File Permissions with Ant »

Deploying to Multiple Rails Environments

Listen to this articleListen to this article

On one Rails project, we have two deployment environments: production; and UAT. Using the default Capistrano configuration makes deploying to these two environments rather difficult so, I thought I'd share our deploy.rb with a bit of explanation along the way. Ok, here goes:

For a start, we deploy to a directory that includes the environment as part of the path:

set :deploy_to, lambda { "/home/#{user}/www/#{rails_env}" }

For subversion, we checkout the code as the user who is running the deployment making sure not to cache authentication details on the server:

set :svn_user, ENV['USER']
set :svn_password, lambda { Capistrano::CLI.password_prompt('SVN Password: ') }
set :repository, lambda { "--username #{svn_user} --password #{svn_password} --no-auth-cache svnurl/trunk/#{application}" }

In both cases, we run a mongrel cluster. Because the mongrel configuration files share a lot in common and because they largely duplicate information contained within the deployment script, we generate an appropriate configuration on deployment. More of that in a bit but for now, the common bits look like:

set :mongrel_address, "127.0.0.1"
set :mongrel_environment, lambda { rails_env }
set :mongrel_conf, lambda { "#{current_path}/config/mongrel_cluster.yml" }

Now, for the environment specific portions. For each environment we have a task that simply sets variables appropriately—I toyed with using an environment variable such as RAILS_ENV rather than the pseudo-tasks but it was more typing and I'm allergic to typing :).

For production, we want 3 mongrel instances in the cluster, listening on ports 8000-8002:

desc "Production specific setup"
task :production do
  set :rails_env, :production
  set :mongrel_servers, 3
  set :mongrel_port, 8000
end

For UAT, we want 2 mongrel instances in the cluster, listening on ports 8010-8011:

desc "UAT specific setup"
task :uat do
  set :rails_env, :uat
  set :mongrel_servers, 2
  set :mongrel_port, 8010
end

And finally, a custom deployment script based almost entirely on the built-in deploy_with_migrations with the major difference being the configuration of the mongrel cluster just prior to restart:

desc "Generic deployment"
task :deploy do
  update_code

  begin
    old_migrate_target = migrate_target
    set :migrate_target, :latest
    migrate
  ensure
    set :migrate_target, old_migrate_target
  end

  symlink
  
  configure_mongrel_cluster

  restart
end

That's it really. Now whenever we need to deploy to a particular environment, say for example UAT, we do something like:

cap uat deploy

Update: By request, here is our database.yml file :

common: &common
  adapter: postgresql
  username: <%= ENV['USER'] %>

development:
  database: foo_development
  <<: *common

test:
  database: foo_test
  <<: *common

uat:
  database: foo_uat
  <<: *common

production:
  database: foo_production
  <<: *common

As you can probably tell, we're lucky enough that the database user is always the same as the user under which the application will be run and is that the database itself is named according to the environment. That makes it very easy to wrap up most of the common parts—Thanks goes to Jon Tirsen for that YAML tip.

This could also easily be generated. I guess it just hasn't needed any attention since it was created so YAGNI overrode DRY ;-)

Comments

Every step made towards "enterprise" rails is exciting. Great work!

Nic

It's certainly something on my agenda :)

how do you manage your different database.yml files?

I've added the database configuration we use.

Cheers,

Simon

Can you explain the use of lambda?

Lambda is a way of creating a block, similar to but not exactly the same as using Proc.new().

Post a comment