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:
- Multiple Heroku remotes: One for staging and one for production.
- Backups as part of the deploy process, so its easy to roll back mistakes.
- Automatic tagging of deployments.
I'll explain how and why we did all of this shortly, but first things first…
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 heroku.com. 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 firstname.lastname@example.org:myapp.git 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
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 email@example.com:myapp.git heroku-2010-02-01:master
If you do something different it should be easy enough to change the scripts to suit.
I've put the code on GitHub in a gist, feel free to fork and improve it.