How to use distillery to run migrations on update

The goal of this post is two-fold, to write my first blog post that will hopefully help someone who winds up stuck in the same rut that I was in and to fill in some small holes in otherwise very helpful articles. The content and a lot of references in this post come from a post I used to figure this out!

Deploying with Phoenix is not straight forward but distillery and edeliver have made it a lot easier to manage releases and deployments without sacrificing the ability to use the hot upgrades feature. One feature which makes deployments smoother for me is automating database migrations on update - a feature that neither of these tools has out of the box.

Boot Hooks, however, do come out of the box and provide a means for doing any preparation or clean up at os level via an executable. Incorporating boot hooks requires changes in three places. Before continuing make sure you have distillery and edeliver installed and the config files for each created.

Release Configuration Change

The first change happens in rel/config.exs.

environment :dev do
  set dev_mode: true
  set include_erts: false
  set post_start_hook: "rel/hooks/post_start"
  ...
end


environment :prod do
  set include_erts: true
  set include_src: false
  set post_start_hook: "rel/hooks/post_start"
  ...
end

The post start hook is one of the boot hooks specified by distillery. A more complete list can be found here. These refer to executables in the specified directory. For this example we will be using shell scripts to begin the migration process.

Execute Migration Task From Shell Script

To begin, create a new rel/hooks directory and create a file post_start[.sh] in it.

set +e

echo "Preparing to run migrations"

while true; do
  nodetool ping
  EXIT_CODE=$?
  if [ $EXIT_CODE -eq 0 ]; then
    echo "Application is up!"
    break
  fi
done

set -e

echo "Running migrations"
bin/seasaltearrings rpc Elixir.Release.Tasks migrate
echo "Migrations run successfully"

This script will execute after the Elixir process has begun so it takes advantage of erlang’s rpc module to execute a task on the running process. This leads us to the last step to setting up a boot hook with Distillery and that is creating a task module in Elixir. There is no task module with the phoenix template so you will have to add one and reference in mix.exs. I chose to put the tasks file in release/tasks.ex and added release to the compile path in mix.exs.

# Specifies which paths to compile per environment.
 defp elixirc_paths(:test), do: ["lib", "test/support"]
 defp elixirc_paths(_),     do: ["lib", "release"]
 ...

The module itself simply ensures the application is running and uses the ecto migrator module to ensure that all migrations in priv/repo/migrations have been completed.

 defmodule Release.Tasks do
  def migrate do
    {:ok, _} = Application.ensure_all_started(:seasaltearrings)

    path = Application.app_dir(:seasaltearrings, "priv/repo/migrations")

    Ecto.Migrator.run(Seasaltearrings.Repo, path, :up, all: true)
  end
end

Now running mix release will generate a release that will automatically attempt to run migrations with /bin my_app foreground locally and on the staging and production host if you are using edeliver.

If you have made it all the way through this post I want to thank you and would welcome any feedback at me@samwhunter.com.