Change my mind: Migrations in a start phase

# in my mix.exs

  def application do
      mod: {My.Application, []},
      start_phases: [{:migrate, []}],
      extra_applications: [:logger, :runtime_tools]
# in lib/my/application.ex

  def start_phase(:migrate, _, _) do
    Ecto.Migrator.with_repo(My.Repo, &, :up, all: true))

Works everytime™



I think the main downside compared to having the migrator in your supervision tree is that it doesn’t let you control where the migration happens relative to other items in your supervision tree. For example, we have basically:

        {Absinthe.Subscription, Sensetra.Endpoint},
        ... other children

This is important because it allows the Ingestion.Super process to be sure that any database changes it relies on have definitely have happened by that point because the Migrator has run.

You’ll also note that I start the Endpoint pretty early. The way that works is that the /alive path returns true, but the /ready path returns false. This lets Kubernetes know that the pod is alive and running, but is not yet ready to receive traffic. It can take its time to run migrations, get the process tree up and running, and then the DeploymentNotifier child sets an application environment value such that /ready returns true.


One reason would be long running migrations, which effectively block each instance of your app from starting up.

Which is not necessarily to say don’t do it. But, be careful what you do there. Some migrations are better to run live, like stuff that transforms data, fills in a column with a default, etc.


Awesome answer, thank you.

Having a sensible split between /ready and /alive is a massive improvement and a great example on why to keep migrations situated in the supervision tree.

I tend to avoid doing long running migrations as a ORM-driven migration.

My approach there has always been to rebuild tables and once they are ready to swap them instantaneously. Doing that in the database itself, rather than coupling any application code to it.

I tend to think that the classical DBA approach sometimes has some value, as it is decoupling responsibilities from application development into database management.
And I do not think one should always hire a DBA, but just splitting those into separate tasks is usually de-escalating some heavier work on the database into its own domain. The application can keep running happily while the database is being worked on separately.

For example migrations in the supervision tree do not shield you from the problem, that your liveness-probe times out because of a long running migration.


Yeah I also avoid long running migrations. Anything that could take a long time I always just find a backwards compatible way to do and then I do it via psql and let it take however long it needs while production carries on happily. Afterward I write an idempotent migration mostly to keep everyone’s dev / test environments up to date, and then deploy that, which should migrate instantly.