public
Last active — forked from RSpace/deploy.rake

Rakefile to deploy and rollback to Heroku in two different environments (staging and production) for the same app

  • Download Gist
deploy.rake
Ruby
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90
#Deploy and rollback on Heroku in staging and production
task :deploy_staging => ['deploy:set_staging_app', 'deploy:push', 'deploy:restart', 'deploy:tag']
task :deploy_production => ['deploy:set_production_app', 'deploy:push', 'deploy:restart', 'deploy:tag']
 
namespace :deploy do
PRODUCTION_APP = 'YOUR_PRODUCTION_APP_NAME_ON_HEROKU'
STAGING_APP = 'YOUR_STAGING_APP_NAME_ON_HEROKU'
 
task :staging_migrations => [:set_staging_app, :push, :off, :migrate, :restart, :on, :tag]
task :staging_rollback => [:set_staging_app, :off, :push_previous, :restart, :on]
 
task :production_migrations => [:set_production_app, :push, :off, :migrate, :restart, :on, :tag]
task :production_rollback => [:set_production_app, :off, :push_previous, :restart, :on]
 
task :set_staging_app do
APP = STAGING_APP
end
 
task :set_production_app do
APP = PRODUCTION_APP
end
 
task :push do
puts 'Deploying site to Heroku ...'
puts `git push -f git@heroku.com:#{APP}.git`
end
task :restart do
puts 'Restarting app servers ...'
puts `heroku restart --app #{APP}`
end
task :tag do
release_name = "#{APP}_release-#{Time.now.utc.strftime("%Y%m%d%H%M%S")}"
puts "Tagging release as '#{release_name}'"
puts `git tag -a #{release_name} -m 'Tagged release'`
puts `git push --tags git@heroku.com:#{APP}.git`
end
task :migrate do
puts 'Running database migrations ...'
puts `heroku rake db:migrate --app #{APP}`
end
task :off do
puts 'Putting the app into maintenance mode ...'
puts `heroku maintenance:on --app #{APP}`
end
task :on do
puts 'Taking the app out of maintenance mode ...'
puts `heroku maintenance:off --app #{APP}`
end
 
task :push_previous do
prefix = "#{APP}_release-"
releases = `git tag`.split("\n").select { |t| t[0..prefix.length-1] == prefix }.sort
current_release = releases.last
previous_release = releases[-2] if releases.length >= 2
if previous_release
puts "Rolling back to '#{previous_release}' ..."
puts "Checking out '#{previous_release}' in a new branch on local git repo ..."
puts `git checkout #{previous_release}`
puts `git checkout -b #{previous_release}`
puts "Removing tagged version '#{previous_release}' (now transformed in branch) ..."
puts `git tag -d #{previous_release}`
puts `git push git@heroku.com:#{APP}.git :refs/tags/#{previous_release}`
puts "Pushing '#{previous_release}' to Heroku master ..."
puts `git push git@heroku.com:#{APP}.git +#{previous_release}:master --force`
puts "Deleting rollbacked release '#{current_release}' ..."
puts `git tag -d #{current_release}`
puts `git push git@heroku.com:#{APP}.git :refs/tags/#{current_release}`
puts "Retagging release '#{previous_release}' in case to repeat this process (other rollbacks)..."
puts `git tag -a #{previous_release} -m 'Tagged release'`
puts `git push --tags git@heroku.com:#{APP}.git`
puts "Turning local repo checked out on master ..."
puts `git checkout master`
puts 'All done!'
else
puts "No release tags found - can't roll back!"
puts releases
end
end
end

You don't need to restart manually after a push, nor do you need to turn on/off maintenance mode. Heroku will auto-restart your app servers upon deploy.

The problem is that you can have a lag beetwen the time of code pushed/server restarted and the migration executed (so for some seconds there can be some users navigating parts of your app with the new code that requires new data structure giving 500 errors).

Got, and used. Great stuff.

Oh...thank you! It's a pleasure! :)

Bye,
N.

On Wed, Jun 8, 2011 at 11:53 AM, toastkid <
reply@reply.github.com>wrote:

Got, and used. Great stuff.

Reply to this email directly or view it on GitHub:
https://gist.github.com/362873

Nicola Junior Vitto
M +39 339 3993281
http://njvitto.com
http://blomming.com
mailto:njvitto@gmail.com
G-Talk: njvitto@gmail.com | Skype: njvitto
In: http://www.linkedin.com/in/nicolajuniorvitto
FF: http://friendfeed.com/njvitto
T: http://twitter.com/njvitto
F: http://facebook.com/njvitto

Shouldn't you turn off the app before the push, since the push will have code that requires the new schema?

Yes. Sometimes I used that way...but right now I prefer to leave the app up
and running. The new code with the old schema is better than the app down :)

On Tue, Jun 21, 2011 at 1:01 AM, jksilliman <
reply@reply.github.com>wrote:

Shouldn't you turn off the app before the push, since the push will have
code that requires the new schema?

Reply to this email directly or view it on GitHub:
https://gist.github.com/362873

Thanks for sharing, I looked at the heroku_san gem and release management add-on but this seems to be the most elegant solution.

Do you use the task :deploy_staging or deploy:staging_migrations ?

It takes longer if you try to run migrations, so I typically use
:deploy_staging unless I know that there are migrations which need to be
run.

On Tue, Oct 4, 2011 at 12:40 PM, Leo Romanovsky <
reply@reply.github.com>wrote:

Do you use the task :deploy_staging or deploy:staging_migrations ?

Reply to this email directly or view it on GitHub:
https://gist.github.com/362873

Yes it's safer and faster to generally use the :deploy_staging task if you haven't new migrations or if the code is backward compatible.
So use the deploy:staging_migrations only if you have new migrations to run.

Of course, I really wish the script could detect whether or not there are
new migrations, because forgetting to run the migrations is a problem.

On Wed, Oct 5, 2011 at 6:07 AM, Nicola Junior Vitto <
reply@reply.github.com>wrote:

Yes it's safer and faster to generally use the :deploy_staging task if you
haven't new migrations or if the code is backward compatible.
So use the deploy:staging_migrations only if you have new migrations to
run.

Reply to this email directly or view it on GitHub:
https://gist.github.com/362873

Anyone is really welcome to upgrade the script :)
Anyway is not a problem to run deploy:staging_migrations at every deploy if you are afraid to forget the migrations...

Is there a reason you put the app into maintenance mode when pushing?

@jksilliman - i think you could test if the migrations have changed by doing

if `git diff origin/HEAD HEAD`.include?("/db/migrate/")

and then decide whether you wanted to put it into maintenance mode during the push.

Hi cj, no it isn't and you haven't (if you see the deploy_staging and deploy_production tasks, they are without migrations).
As for the process to deploy the code I think that the safest way is something like this:

1) Commit/Push your safe code on git/Github DEVELOP (after merging from a specific branch, if needed) with only the migrations that will be subsequently useful
2) Deploy (without migrations) on Heroku STAGING your safe code with only the migrations that will be subsequently useful. You can use this script on the root:

"rake deploy_staging"

3) Run heroku rake db:migrate on STAGING

4) Commit/Push your safe code on git/Github MASTER (after merging from DEVELOP branch) with only the migrations that will be subsequently useful

5) Deploy (without migrations) on Heroku PRODUCTION your safe code with only the migrations that will be subsequently useful. You can use this script on the root:

"rake deploy_production"

6) Run heroku rake db:migrate on PRODUCTION (WARNING: for critical migrations, such as ALTER TABLE, please consider to start an "Heroku Follower", as rollback in bad cases and to drop after the migration was run)

7) Back to the code: now is the moment to commit/push on git/Github DEVELOP the new code that uses the new migrations

8) Deploy (without migrations) on Heroku STAGING your safe code that uses the new migrations run in step 3

9) Commit/Push your safe code on git/Github MASTER (after merging from DEVELOP branch) the new code that uses the new migrations

10) Deploy (without migrations) on Heroku PRODUCTION your safe code that uses the new migrations run in step 6

Please note that this process is safe in every moment. For instance:

  • There isn't any kind of downtime deploying code that must use new migrations (waiting for rake timing of running or that heroku restarts the servers)
  • If another developer pushes new code all should be good

Hope this helps..

Bye,
Nicola.

For the Cedar stack, you need to modify the deploy:migrate task as follows:

task :migrate do
puts 'Running database migrations ...'
puts heroku run rake db:migrate --app #{APP}
end

Note the word run inserted between "heroku" and "rake".

Hey, thanks for the nice gist.

I stumbled upon a problem with new bundler 1.2.0.pre. (I'm using rbenv for ruby 1.9.3 installing). When I try the run heroku commands from within the rake task, I got an bundler error: our Ruby version is 1.9.2, but your Gemfile specified 1.9.3.

I know, that is not problem specific to this gist, but maybe you have an idea to solve this?

Best,
benni

Very convenient, thanks for sharing!

nice gist! there's a rollback feature in heroku (maybe this is new). heroku rollback --app app_name, so you don't actually need to tag the releases yourself now. heroku releases --app app_name

I try running heroku run rake deploy but i get an error on my app:
/app/vendor/bundle/ruby/1.9.1/gems/rake-10.0.4/lib/rake/task_manager.rb:49:in []'
/app/vendor/bundle/ruby/1.9.1/gems/rake-10.0.4/lib/rake/application.rb:142:in
invoke_task'
/app/vendor/bundle/ruby/1.9.1/gems/rake-10.0.4/lib/rake/application.rb:101:in block (2 levels) in top_level'
/app/vendor/bundle/ruby/1.9.1/gems/rake-10.0.4/lib/rake/application.rb:101:in
each'
/app/vendor/bundle/ruby/1.9.1/gems/rake-10.0.4/lib/rake/application.rb:101:in block in top_level'
/app/vendor/bundle/ruby/1.9.1/gems/rake-10.0.4/lib/rake/application.rb:110:in
run_with_threads'
/app/vendor/bundle/ruby/1.9.1/gems/rake-10.0.4/lib/rake/application.rb:95:in top_level'
/app/vendor/bundle/ruby/1.9.1/gems/rake-10.0.4/lib/rake/application.rb:73:in
block in run'
/app/vendor/bundle/ruby/1.9.1/gems/rake-10.0.4/lib/rake/application.rb:160:in standard_exception_handling'
/app/vendor/bundle/ruby/1.9.1/gems/rake-10.0.4/lib/rake/application.rb:70:in
run'
/app/vendor/bundle/ruby/1.9.1/gems/rake-10.0.4/bin/rake:33:in <top (required)>'
/app/vendor/bundle/ruby/1.9.1/bin/rake:19:in
load'
/app/vendor/bundle/ruby/1.9.1/bin/rake:19:in `'
Would you know what should I do to fix this ?

Made a few abstractions, additions, protections https://gist.github.com/jphenow/5694169

Extracting the tasks into a class for easier extension:

https://gist.github.com/jfeaver/9820478

Thanks for the great foundation work @njvitto!

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.