65

I have a slight annoyance with my heroku push/deploy process, which otherwise has been a joy to discover and use.

If i add a new migration to my app, the only way i can get it up onto the heroku server is to do a push to the heroku remote. This uploads it and restarts the app. But it doesn't run the migration, so i have to do heroku rake db:migrate --app myapp, then heroku restart --app myapp. In the meantime, the app is broken because it hasn't run the migrations and the code is referring to fields/tables etc in the migration.

There must be a way to change the deployment process to run the rake db:migrate automatically as part of the deploy process but i can't work it out.

Is it something i set in a heroku cpanel? Is it an option i pass to heroku from the command line? Is it a git hook? Can anyone set me straight? thanks, max

Max Williams
  • 32,435
  • 31
  • 130
  • 197

10 Answers10

49

Heroku now has the ability to handle this as part of their "release phase" feature.

You can add a process called release to your Procfile and that will be run during each and every deploy.

Rails >= 5 Example

release: bundle exec rails db:migrate

Rails < 5 example

release: bundle exec rake db:migrate

Max Woolf
  • 3,988
  • 1
  • 26
  • 39
  • Gave the +1. I had to change it to `rake db:migrate` – Loubot Aug 20 '16 at 08:09
  • yeah, Rails 5 replaces `rake` globally with the `rails` binary. I'll update my answer. – Max Woolf Aug 22 '16 at 10:36
  • Cheers for that. Haven't moved to rails 5 yet. – Loubot Aug 22 '16 at 14:50
  • 2
    docs: https://devcenter.heroku.com/articles/release-phase Note release phases are in Beta and the commands run when a new release is created (ie 1. an app build 2. a pipeline promotion 3. a config var change 4. a rollback 5. a rlease via the Platform API) – hajpoj Aug 26 '16 at 21:04
33

What about this simple command chaining solution:

git push heroku master && heroku run rake db:migrate

It will automatically run the migrate as soon as the first one finishes successfully. It's tipically 1-2 seconds delay or less.

Cristian
  • 5,877
  • 6
  • 46
  • 55
29

Here is a rake task that wraps up everything into a one-liner (and also supports rollback):

https://gist.github.com/362873

You still might wind up deploying on top of your boss's demo, but at least you don't waste time typing between the git push and the rake db:migrate.

Paul A Jungwirth
  • 23,504
  • 14
  • 74
  • 93
  • 1
    I preferred an object-oriented version of the gist mentioned: https://gist.github.com/jfeaver/9820478 – Nathan Mar 27 '14 at 22:33
  • This is great, it looks .. almost exactly like the one I use. =) – reconbot Dec 15 '14 at 16:17
  • 3
    I think this gist may be a bit outdated now - the code seems like it is from heroku's earlier days when more manual intervention was required after a push – cwd Aug 25 '15 at 01:13
14

I created a custom buildpack that gets Heroku to run rake db:migrate for you automatically on deployment. It's just a fork of Heroku's default Ruby buildpack, but with the rake db:migrate task added.

To use it with your app you'd do this:

heroku config:set BUILDPACK_URL=https://github.com/dtao/rake-db-migrate-buildpack

Also note that in order for it to work, you need to enable the user-env-compile Heroku Labs feature. Here's how you do that:

heroku labs:enable user-env-compile

And here's my evidence that this works:

rake db:migrate on Heroku deployment

Dan Tao
  • 125,917
  • 54
  • 300
  • 447
  • 1
    YES. 10 seconds of work to set up and it's exactly what I was looking for. Much better than the other answers here, at least for my purposes. Thanks. – GMA Nov 07 '13 at 07:04
  • My recommendation would be to fork the project and use the fork as the deploy process. Because here you trust a Github repo to deploy your production code. – Dorian Mar 10 '14 at 13:47
  • 2
    Good point. However, this repo doesn't work anymore anyway because [`user-env-compile`](https://devcenter.heroku.com/articles/labs-user-env-compile), which it depends on, has been removed. :( – GMA Apr 13 '14 at 05:39
9

Perhaps you could try separating your schema commits (migrations, etc.) commits from code commits (models, validations, etc.).

(Note the following assumes your migration changes are NOT destructive, as you've indicate covers most of your use cases.)

Your deploy process could then be:

  1. Push schema changes to Heroku
  2. migrate
  3. Push application code to Heroku

This is of course far form optimal, but is an effective way to avoid downtime in the situation you've described: by the time the app receive the code for the dynamic fields, the DB will already have migrated.

(Of course, the simplest solution would be to simply push and migrate while your boss is out to lunch ;-D)

Otherwise, even if schema modifications were carried out automatically you'd still run the risk of a request passing through right before the migrations have been run.

David Sulc
  • 25,946
  • 3
  • 52
  • 54
5

Just for those googling folks like me I want to give a plain solution here.

I am using Rails 4 and needed to add a simple Rake task to the deployment to heroku. As I am using the 'deploy to heroku' button in github there is no chance to run "heroku run ..." immediately after deployment.

What I did: I extended the standard Rake Task 'assets:clean' that is automatically run during a deployment to heroku. The task still runs normally but I have attached my own stuff to it's end. This is done with the 'enhance' method. In the example below I add a db:migrate because this is probably what most people want:

# in lib/tasks/assets_clean_enhance.rake
Rake::Task['assets:clean'].enhance do
  Rake::Task['db:migrate'].invoke
end

I admit that this is no perfect solution. But the heroku Ruby Buildpack still does not support any other way. And writing my own buildback seemed a bit of an overkill for so simple a thing.

Kari
  • 249
  • 3
  • 6
2

I wrote SmartMigrate buildpack which is a simple Heroku buildpack to warn of pending migrations after a ruby build whenever new migrations detected. This buildpack is intended to be part of a Multipack that has a preceding Ruby buildpack.

With due respect to other solutions here, this buildpack has 3 advantages over those:

  1. No need for maintenance mode
  2. No need for out-dated ruby buildpack forks that just insert the migration at the end
  3. No need to run migrations ALL THE TIME, a warning is only displayed if new migrations are detected since the last deployment
hammady
  • 969
  • 1
  • 13
  • 22
2

I use a rake task to put the app in maintenance mode, push, migrate and move it off maintenance mode.

Aditya Sanghi
  • 13,370
  • 2
  • 44
  • 50
  • That's just not satisfactory i'm afraid: i want to be able to deploy at any time without worrying about whether my boss is demoing the site to clients or whatever (at which time the appearance of a maintenance page would mean a very angry boss). Thanks for answering though :) – Max Williams May 11 '11 at 15:32
  • 3
    Do you really want to be deploying while your short-fused boss is demoing? I'm not sure heroku provides this feature as yet! The best thats currently possible is no human wait in the meantime. – Aditya Sanghi May 11 '11 at 15:38
  • I don't want to but it inevitable happens :) In my non-heroku apps it's fine as long as the migration is non-destructive, which it almost always is (ie they are usually to add new fields or new tables, rather than delete anything). So, you have an interim period where the old code is running with the new schema, which is generally fine (as the schema only contains additions), then after the migration finishes a rolling restart of the mongrels/unicorns happpen, which has very little if any noticeable downtime/slowdown. – Max Williams May 11 '11 at 15:55
  • i ended up going with your solution after Paul (below) posted an actual rake task up. Guess i was just too lazy to write my own. Thanks :) – Max Williams Jun 08 '11 at 09:53
1

I think David Sulc's approach is the only one which ensures that you avoid requests getting through while the app is in a broken state.

It is a bit of a pain, but may be necessary in some circumstances.

As he stated, it does require that the db migrations are non-destructive.

However, it can be difficult to push your migrations and schema changes prior to the rest of the code, as the obvious approach ('git push heroku {revnum}') relies on you having checked the migrations in before the rest of the code.

If you haven't done that, it's still possible to do this using a temporary branch:

  • Create a branch, based at the git revision that you most recently pushed to heroku:

    git branch <branchname> <revnum-or-tag>
    
  • Check out that branch:

    git checkout <branchname>
    
  • If your db migration commits only contained migrations, and no code changes, cherry-pick the commits that contain the database changes:

    git cherry-pick <revnum1> <revnum2>...
    
  • If you commited your db changes in revisions which also contained code changes, you can use 'git cherry-pick -n' which won't automatically commit; use 'git reset HEAD ' to remove the files that aren't db changes from the set of things that are going to be commited. Once you've got just the db changes, commit them in your temporary branch.

    git cherry-pick -n <revnum1> <revnum2>...
    git reset HEAD <everything that's modified except db/>
    git status
    ... check that everything looks ok ...
    git commit
    
  • Push this temporary branch to heroku (ideally to a staging app to check that you've got it right, since avoiding downtime is the whole point of jumping through these hoops)

    git push heroku <branchname>:master
    
  • Run the migrations

    heroku run rake db:migrate
    
  • At this point, you might think that you could just push 'master' to heroku to get the code changes across. However, you can't, as it isn't a fast-forward merge. The way to proceed is to merge the remainder of 'master' into your temporary branch, then merge it back to master, which recombines the commit histories of the two branches:

    git checkout <branchname>
    git merge master
    git diff <branchname> master
    ... shouldn't show any differences, but just check to be careful ...
    git checkout master
    git merge <branchname>
    
  • Now you can push master to heroku as normal, which will get the rest of your code changes across.

In the second-to-last step, I'm not 100% sure whether merging master to {branchname} is necessary. Doing it that way should ensure that a 'fast-forward' merge is done, which keeps git happy when you push to heroku, but it might be possible to get the same result by just merging {branchname} to master without that step.

Of course, if you aren't using 'master', substitute the appropriate branch name in the relevant places above.

sheltond
  • 1,877
  • 11
  • 15
  • You can force push to heroku so you can skip all those merges and just force push master after running the migration. – eloyesp Mar 13 '16 at 00:13
0

I've been using the heroku_san gem as my deployment tool for a while. It is a nice small, focused tool for the push + migration. It adds some other rake commands that make accessing other functions (like console) easy. Beyond not having to remember database migrations, my favorite feature is its Heroku configuration file – so I can name all my servers (production, staging, playground4, shirley) whatever I want – and keep them straight in my head.

slothbear
  • 2,016
  • 3
  • 21
  • 38