Deploying to Multiple Rails Environments
Listen 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
Posted by: Dr Nic | September 29, 2006 08:40 PM
It's certainly something on my agenda :)
Posted by: Simon Harris
|
September 29, 2006 11:55 PM
how do you manage your different database.yml files?
Posted by: bryanl | October 1, 2006 04:09 AM
I've added the database configuration we use.
Cheers,
Simon
Posted by: Simon Harris
|
October 1, 2006 08:03 AM
Can you explain the use of lambda?
Posted by: Al | January 24, 2008 06:04 AM
Lambda is a way of creating a block, similar to but not exactly the same as using Proc.new().
Posted by: Simon Harris
|
January 24, 2008 06:54 AM