When Pushing Just Isn't Getting the Job Done
Like any good development shop we are constantly looking to make commonly recurring tasks more easily repeatable. Code deployment is one of those things that is happening throughout the day. With Heroku being our hosting provider of choice it makes sense to make deploying our projects as easy as possible.
![Paratrooper Image][heading_image]
Some of you may be thinking, 'wait a minute... isn't a deployment to Heroku just a git push away?' You would be correct. It is just that easy to deploy and I want to personally congratulate whoever came up with that idea!
Sometimes projects just require a couple extra steps when deploying. For example a common checklist for deploying a rails app may look like this:
- Turn maintenance page on
- Git push your changes to Heroku
- Run database migrations
- Turn maintenance page off
These are all things we are doing right? Instead of performing multiple tasks in the command line, you've probably made a [rake task][] or some flavor of task to handle this. This is mostly a great solution to the problem, but let me try to make this a bit easier. Enter [Paratrooper][], a library to make your Heroku deployment rake tasks simple and powerful.
Let's take a look at what our previous deployment task would look like:
namespace :deploy do
desc 'Deploy the app'
task :production do
app = "amazing-production-app"
remote = "git@heroku.com:#{app}.git"
system "heroku maintenance:on --app #{app}"
system "git push #{remote} master"
system "heroku run rake db:migrate --app #{app}"
system "heroku maintenance:off --app #{app}"
end
end
This makes sense and handles our basic deployment case. Now check out how this changes with Paratrooper:
require 'paratrooper'
namespace :deploy do
desc 'Deploy app in production environment'
task :production do
Paratrooper::Deploy.new("amazing-production-app").deploy
end
end
Ok... that's definitely less code but what is it doing? This will perform the following tasks:
- Activating maintenance mode
- Push changes to Heroku
- Run database migrations
- Restart the application
- Deactivate maintenance mode
- Warm application instance
Whoa, one statement and we get all of this? That's pretty awesome and easily repeatable. We've had a very easy time porting applications over to Paratrooper just because these are common tasks that we are doing on most projects. Now what happens when my project needs a little bit more deployment love? Well, Paratrooper essentially gives you a bunch of convienence methods that cover common steps of the deployment process. Which means you can just call these methods in any particular order or add in your own code in between. Maybe you have to send a call to [New Relic][] to turn off application monitoring during your deploy. That would probably look like this:
require 'paratrooper'
namespace :deploy do
desc 'Deploy app in production environment'
task :production do
deployment = Paratrooper::Deploy.new("amazing-production-app")
%x[curl https://heroku.newrelic.com/accounts/ACCOUNT_ID/applications/APPLICATION_ID/ping_targets/disable -X POST -H "X-Api-Key: API_KEY"]
deployment.activate_maintenance_mode
deployment.push_repo
deployment.run_migrations
deployment.app_restart
deployment.deactivate_maintenance_mode
deployment.warm_instance
%x[curl https://heroku.newrelic.com/accounts/ACCOUNT_ID/applications/APPLICATION_ID/ping_targets/enable -X POST -H "X-Api-Key: API_KEY"]
end
end
Now you're getting simplicity as well as flexibility!
One more tidbit for you. I am a fan of using tags for managing where my code is at in different environments. So I've built in the ability to utilize tags for deployment. Take the common scenario where you have both a staging and production app on Heroku. This is what my deployment tasks would look like:
require 'paratrooper'
namespace :deploy do
desc 'Deploy app in staging environment'
task :staging do
deployment = Paratrooper::Deploy.new("amazing-staging-app",
tag: 'staging'
)
deployment.deploy
end
desc 'Deploy app in production environment'
task :production do
deployment = Paratrooper::Deploy.new("amazing-production-app",
tag: 'production',
match_tag_to: 'staging'
)
deployment.deploy
end
end
Hmm, so what did this change? Well we passed in a :tag
and :match_tag_to
option. For the staging
task it will create a 'staging' tag at HEAD
and push that tag to your repo and Heroku. Then for production
the same happens except the :match_tag_to
option is telling Paratrooper to match the 'production' tag where 'staging' is at and then push the 'production' tag to your repo and Heroku.
This is [Paratrooper][], I hope you like and find it as useful as I have. Feel free to provide feedback and comment on how it has helped you.
[Heroku]: http://heroku.com [rake task]: http://blog.hashrocket.com/posts/heroku-deploy-scripts [Paratrooper]: https://github.com/mattpolito/paratrooper [New Relic]: http://newrelic.com [Paratrooper Image]: http://f.cl.ly/items/1w392k0W170t0k0i1S3a/paratroopers.jpg [heading_image]: http://f.cl.ly/items/2P433z3L3I080c3g001d/paratrooper_heading.gif