Staging Environments in Rails
Most Rails apps that grow beyond the “toy” or “small” stage benefit greatly from the addition of a staging environment. Staging is where you deploy to flush out integration issues, to demo new features to users and clients, and generally put the app through its paces in a “production-like” environment before doing a real production deployment. It should match the production environment as closely as possible, though in practice things do diverge for certain cases and for most apps the cost of truly duplicating production hardware is prohibitive.
Rails lets you setup a staging environment easily. Create a file in config/environments called ‘staging.rb’, and then start your server(s) with RAILS_ENV=staging, and you are all set. Here’s quick steps to get up and running with a staging environment:
- copy config/environments/production.rb to staging.rb
- add an entry to database.yml for staging
tweak your deployment to respect your new environment using multistage or something simple like this:
[ruby] task :production do set :rails_env, "production" role :web, "prod.ip.here" # other roles... end task :staging do set :rails_env, "staging" role :web, "stage.ip.here" # other roles end [/ruby]setup and deploy staging:
cap staging setup; cap staging deploy:cold; etc...
- tweak and iterate as you see fit
You may ask at this point “why not just a copy of my production stack as staging?” The fundamental reason is that abstractions leak. Your staging environment is similar to production, but its not an exact copy. Think about email notifications - do you really want exception notifications or activation messages to be send the same in staging as production? Usually the answer is no - you want to be able to tell immediately if an error is from prod or staging, which could mean prepending [STG] in the subject and maybe changing the sender to be staging@domain.com. For a lot of apps, you will have tasks that will perform differently between staging and production. Some typical processes like this includes batch jobs, web service calls, and all sorts of notification jobs that would notify users or do a real external call, but in staging should just do a fake call and log the result.
To enable easier testing and cleaner code, a very simple wrapper class around RAILS_ENV is nice to have. It also makes me happier to have less constants in my code, as I feel all caps detracts from the beauty of Ruby. You can use Coda’s plugin or write your own in probably 5 minutes. Since you have a level of indirection around your environment checks, you can write specs like this:
[ruby]it “should only log notifier emails in staging” do Rails.stubs(:staging).returns(true) AppNotifier.expects(:debug).returns(mock(“logger”, :debug => “some logger call”)) AppNotifier.send_reports end
it “should send emails in production” do Rails.stubs(:production).returns(true) AppMailer.expects(:deliver_reports) AppNotifier.send_reports end [/ruby]
So don’t be afraid to create a separate staging environment and give it the respect it deserves when your deployment is complex enough to demand it. It will make your life easier and your app easier to maintain and scale.