George Brocklehurst makes things for the internet.

Managing Heroku deployments

Posted on

Heroku is a fantastic service for hosting Ruby applications. It's wonderfully simple to use: You just add Heroku as a Git remote and whenever you want to deploy some changes, you push them to that remote's master branch. Heroku even provide a handy gem that does everything from adding the remote to running tasks on the remote version of your application, all via the heroku command.

If you're already using Git, getting a Rails app set up on Heroku is as simple as running these commands:

gem install heroku
cd myapp
heroku create
git push heroku master
heroku rake db:migrate

This standard workflow is great for the early stages of development or for simple applications, but with a large application you'll probably want to put a few more safeguards in place. Tom Lea and I have recently found ourseveles in exactly that situation, so we've written a few Rake tasks to make sure we don't mess anything up when we're deploying changes.

What we wanted was:

  1. Multiple Heroku remotes: One for staging and one for production.
  2. Backups as part of the deploy process, so its easy to roll back mistakes.
  3. Automatic tagging of deployments.

I'll explain how and why we did all of this shortly, but first things first…

The code

Multiple Heroku remotes

The first thing we wanted to do was set up a second Heroku remote where we could preview and demonstrate changes without endangering the production app.

The heroku command usually figures out which application to send commands to by looking at the Git remotes of the current directory and picking the first one it find that points to This isn't too helpful if you have multiple Heroku remotes, but fortunately there's an optional --app argument which lets you explicity specify which remote you're referring to.

To make sure we don't accidentally run commands against the wrong remote app, we've removed the Heroku git remotes altogether.

Now instead of this:

git push heroku master
heroku rake db:migrate

We use this:

git push master
heroku rake db:migrate --app myapp

Backup before deployment

The second thing we wanted was to be able to roll back quickly if we messed up a deploy. Heroku has a helpful backup feature that allows you to capture, download and most importantly restore a tarball containing the app's code and database using the heroku bundle command. We capture a bundle immediately before deploying and download a copy of it:

heroku bundles:destroy deploybackup --app myapp
heroku bundles:capture deploybackup --app myapp
heroku bundles:download deploybackup --app myapp

The first command (heroku bundles:destroy) will delete the bundle named "deploybackup" from Heroku. If you are using the unlimited bundles add-on you could skip this step and use a unique bundle name each time instead.

Capturing a bundle takes some time, so you can't just run these commands one after another in a script but it is possible to see the current state of your app's bundles using the heroku bundle command, so it's easy enough to wait while Heroku creates the bundle using a while loop and a sleep.

Tagging deployments

Our final aim was to track what we had deployed and when we had deployed it using Git tags. Exactly how you tag and deploy will depend on how you use Git.

For our project, we have a production branch which we merge our changes into when we're ready to deploy them, so our deploy tasks are set up to push the head of that branch. For example, if we were deploying on 1st February 2010, the commands we would use would look something like this:

git tag heroku-2010-02-01 production
git push heroku-2010-02-01:master

If you do something different it should be easy enough to change the scripts to suit.

Fork me

I've put the code on GitHub in a gist, feel free to fork and improve it.


Jason commented on :

Thanks, I've been looking for a staging strategy. I'm gonna give this a shot.

Eric commented on :

Excellent post, thank you for this.

Art commented on :

Cool, had a bad crash on Heroku (their problem when the Rails 2.3.6/2.3.7 change happened over the past several days). My website was down for two days while I struggled to figure things out. If I'd been using your scripts it would have been an easy 'rollback'. At least my butt is in better shape from here on out due to your ideas. Thanks a bunch guys!

Bryan Marble commented on :

Great script. Just a note, if you're copy/pasting from here, make sure to go through and replace the ellipses with three periods. Rake doesn't like the multi-byte characters.

Thanks for the awesome script!

Scott Helm commented on :

Well done. This makes me feel much better about the deployment process. Getting ready to go into production, and this eases my mind quite a bit. Great work!