Run Ecto Migrations in Production with Distillery Boot Hooks

What to do when you can’t mix ecto.migrate

This post was originally published on Sophie’s blog, The Great Code Adventure.

You need to run your migrations with the production release of your Elixir app in your production environment. You can’t use mix! You can use Ecto Migrator. Read on to find out how to run your Ecto migrations in production using Distillery’s Boot Hooks.

The Problem

Coming from a Rails background, I just sort of expected to be able to execute my migrations in the production environment exactly like I would in dev.

mix ecto.migrate

I was confused with my attempts to do so in production were met with:

** (Mix) The task “ecto.migrate” could not be found

Turns out, mix tasks aren’t compiled into the project BEAMs, and therefore they aren’t deployed with the release. Womp womp.

Lucky for use, we can leverage the easy-to-use Ecto Migrator API to define a custom migration task. Then, we can run this task with Distillery’s Boot Hooks.

A Note on Distillery

This problem (no mix in prod) and the solution of using Ecto Migrator was revealed to me by this excellent post by Flatiron Labs engineer Kate Travers. Sadly for me, Kate’s project used eDeliver for deployments, which made it even easier to leverage Ecto Migrator. I had to take a slightly different approach to get my migration script working with Distillery.

Writing a Custom Migration Task with Ecto Migrator

Our custom migration task is pretty simple. We’ll define a module MyApp.ReleaseTasks that looks like this:

Our task does a few things:

  • Make sure the app is started
  • Grab the path to the migrations directory
  • Use Ecto.Migrator to run the migrations against our app’s Repo.

Now that we have a nice tidy function that wraps up our Ecto.Migrator work, let’s write a shell script that Distillery can execute for us when it starts running a production release.

Writing a Migration Shell Script

Leveraging Erlang’s rpc Module

In order to execute our ReleaseTasks.migration/0 function against our production release, we’ll use Erlang’s rpc module. The rpc module provides Remote Produce Call services:

A remote procedure call is a method to call a function on a remote node and collect the answer. It is used for collecting information on a remote node, or for running a function with some specific side effects on the remote node.

rpc calls are executed like this:

:rpc.call(node, module, fun, args)

We can send an rpc call to our production release like this:

bin/my_app rpc “Elixir.MyApp.ReleaseTasks.migrate”

Now that we know how to call on our task, let’s write that shell script!

The Migration Shell Script

Our script is pretty simple, it loops until our application is up and running (with the help of the nodetool command line utility). Then, once the app is up, it uses rpc to execute our migration function.

Now that our shell script is ready to go, we need to teach Distillery when to execute it.

Executing the Migration Script with Distillery’s Boot Hooks

Distillery allows us to execute shell scripts at points in time via Boot Hooks.

Boot hooks are scripts which execute pre/post to events that occur in the run control script.

Whatever scripts we specify as a pre/post hook will get sourced into Distillery’s own boot script at runtime.

We want our migration task to run as a post script, since it requires the app to be running.

In order to leverage boot hooks, we need to do two things:

  • Tell Distillery that we have a post start script for it to run
  • Place the post start script in the correct file path

This will tell Distillery to execute any scripts in the rel/hooks/post_start directory after the app starts up. So, we need to place our migration script in a file:

rel/hooks/post_start/migrate.sh

And that’s it! When we build our production release and run it, our migrations will run.


P.S. Want to work on a mission-driven team that loves Distillery boot hooks and ice cream? We’re hiring!


Footer top

To learn more about Flatiron School, visit the website, follow us on Facebook and Twitter, and visit us at upcoming events near you.

Flatiron School is a proud member of the WeWork family. Check out our sister technology blogs WeWork Technology and Making Meetup.

Footer bottom