Deploying to Staging and Production with Capistrano
Posted by Guy Naor Thu, 28 Sep 2006 17:07:30 GMT
After an application is released, deployment should be in stages. To a test server, a staging server and a production one. I hope you have a staging server...
But capistrano has no knowledge of the difference between a staging and a production server. To make it easy to deploy using capistrano, a small change is needed to deploy.rb.
We add an if statement based off of an environment variable, and with it select the servers to deploy to. I also use this same variable to protect my production servers from accidental data changes, by adding an if to all data altering recipies I use only in staging (like reloading the DB and such). This way there's no way to run those commands on the production server.
Now, to deploy on staging: env DEPLOY='STAGING' cap deploy
And on production: env DEPLOY='PRODUCTION' cap deploy
Here's a small snippet of code with the change:
if ENV['DEPLOY'] == 'PRODUCTION'
puts "*** Deploying to the \033[1;41m PRODUCTION \033[0m servers!"
role :web, "www.your.domain.com"
role :app, "app.your.domain.com
role :db, "db.your.domain.com", :primary => true
else
puts "*** Deploying to the \033[1;42m STAGING \033[0m server!"
role :web, "www.staging.domain.com"
role :app, "app.staging.domain.com
role :db, "db.staging.domain.com", :primary => true
end

















Nice post.
I used similar technique, but I had two capistrano recipes: for production and for staging. All tasks were contained in production deployment recipe and staging recipe just "required" production one and changed some variables (deployto, railsenv, etc.). This way deploying to staging or production is a matter of selecting a deployment recipe file, not some command line argument (which I think is easy to use).
I had a small application so both production and staging were hosted on the same server. So I needed different databases. Thank to Rails, adding new database config is trivial:
== config/database.yml
Also, to be formal - a separate environment setup file in config/environments/.
The interesting part of the setup is managing code repositories. I used Darcs, but I think the same technique can be applied to Subversion as well. E.g. you are working on adding some new feature and want to give your client ability to test it and get some feedback from him. So you deploy your latest source code to staging server. But then you receive a bug report for production site and need to fix it ASAP. You make the changes and need to push it to both staging and production sites. But here is the problem: you don't want to push unfinished features (that were previously pushed to staging) to production.
I solved this problem with having 3 repositories: main repository (were all code is stored), staging repository (where to you can push features for staging from main repository) and production repository (to where you can push tested and confirmed features FROM STAGING REPOSITORY). The need for separate repositories is because of Darcs don't have branches: branching is just making new repositories. I think, in Subversion you can have one repository with branches for staging and production.
The different recipies is a nice idea, but then you need to manage 2 sets of recipies. So it's not completely DRY. And I have many non-standard recipies, as I manage everything with capistrano.
As for the different repositories, every time I deploy anything to production, I create a tag on my SVN repository, and when needed I use this tagged release to update the production server. In svn tags are like branches. You can modify them.
I am using a modified update_code recipe (my servers don't have access to my SVN repository, and so I export the release locally and then upload a tar to the server.), and you can pass a parameter to the recipe (or set an ENV variable) with the revision to use. Here's part of the recipe:
Now all you have to do is:
About DRY with 2 recipes: I just "require" one in another and redefine some variables. Something like that:
== deploy_staging.rb
require 'config/deploy_production' set :deploy_to, '/path/to/#{application}_staging' set :repository, 'http://myhost/mystagingrepository' set :rails_env, :stagingThat's actually much better if you need different recipes.
Though in my case because they are all similar aside from the deploy to stuff, a small if makes it simpler.
Also, I forgot to mention the usage of svnmerge.py to do all the code pushing to production/staging for changes that I need between releases. For those small bug fixes.
Now I'm using a pretty complex script that tag a release, tag all the dependencies and put them all under svnmerge.py control. Then a change I want to put there is either put in place, or I svmmerge.py merge from the trunk. Makes everything much more controllable.