Disposable Staging Site on Heroku

2011-11-19 , , ,

We are building ElectNext on Heroku. Although we’ve had some stability problems, we’re very happy with all the infrastructure they provide us. One advantage is that we can treat applications as “disposable.” We can’t take this as far on Heroku as we could on AWS, but there are still places it comes in handy. One example is for our staging site. We deploy here just before deploying to production, as a final check that everything works as expected. Really, at this point we are testing the act of deploying more than the code itself. The staging site lets us rehearse each deployment.

Of course we want staging to mirror production as closely as possible, but inevitably the two get out of sync. On bare metal, this used to be a thorny problem to solve, but in the cloud it’s easy to throw away staging completely and re-create it based on production. That means the two never drift apart, and we can re-initialize staging just before testing a deploy. This gives us greater confidence that the production deployment will run smoothly. Also, if the deployment to staging fails, then we can re-initialize again so that we are testing our fixes appropriately.

To perform a staging initialization, I wrote the following Makefile. You’ll want to adapt a few things, but it’s pretty generic. It assumes that you have two git remotes, one named heroku (for production) and one named staging. Although on Heroku code usually runs in the Rails production environment, we run staging in a custom staging environment, by setting RACK_ENV=staging. You also may not want the same add-ons, config vars, etc. as are listed here. But hopefully this will be a start!:

# We use this Makefile to destroy the staging site
# and re-create it based on production.
# This is useful to keep staging current,
# and it's especially nice so we can rehearse deployments.
# If we nuke staging and then deploy the latest code there,
# we can be more confident that the production deploy
# will run smoothly.

PRODUCTION_APP=example
STAGING_APP=example-staging

initialize_staging: destroy_staging create_staging populate_staging_database


populate_staging_database:
	heroku pgbackups:capture --expire --app=${PRODUCTION_APP}
	heroku pgbackups:restore DATABASE `heroku pgbackups:url --app=${PRODUCTION_APP}` --app=${STAGING_APP} --confirm ${STAGING_APP}
	# We use these next two Rake tasks so it's easy to get consistent
	# indexes and foreign keys on all systems,
	# and they are all listed in one place.
	# This is especially handy for foreign keys,
	# because Heroku's db:{push,pull} commands don't transfer them.
	heroku rake db:add_indexes	--app=${STAGING_APP}
	heroku rake db:add_foreign_keys	--app=${STAGING_APP}
	heroku ps:restart		--app=${STAGING_APP}


create_staging:
	heroku apps:create --remote staging --stack bamboo-mri-1.9.2 ${STAGING_APP}
	git push staging master
	heroku sharing:add someone@example.com                          --app=${STAGING_APP}
	# We run staging as its own Rails environment:
	heroku config:add RACK_ENV=staging			        --app=${STAGING_APP}
	# Uncomment this line if you want a bigger database on staging:
	# heroku addons:upgrade shared-database:20gb			--app=electnext-staging
	heroku addons:add releases:basic				--app=${STAGING_APP}
	heroku addons:add pgbackups:basic				--app=${STAGING_APP}
	heroku addons:add custom_domains:basic				--app=${STAGING_APP}
	heroku addons:add newrelic:standard   				--app=${STAGING_APP}
	heroku addons:upgrade logging:expanded 				--app=${STAGING_APP}
	heroku domains:add staging.example.com                          --app=${STAGING_APP}
	heroku addons:add memcache:5mb					--app=${STAGING_APP}
	# ...
	heroku ps:restart						--app=${STAGING_APP}


destroy_staging:
	heroku apps:destroy --app=${STAGING_APP} --confirm ${STAGING_APP}


# vim: set filetype=make :
blog comments powered by Disqus Prev: Simple Chef Solo Tutorial Next: Piping in Ruby with popen3