Run Ecto Migrations in Production with Distillery Boot Hooks
What to do when you can’t
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.
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.
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
Ecto.Migratorto run the migrations against our app’s
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
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:
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!