Migration scripts (migrate/rollback/seeding) database when releasing a Phoenix/OTP app by Distillery
Tags: elixir, phoenix
Published: 2018-04-23 10:49:03

---
title: Migration scripts (migrate/rollback/seeding) database when releasing a Phoenix/OTP app by Distillery
published: true
description: 
tags: Elixir, Phoenix
---

Hi everyone!

In this post, I would like to share the migration script (migrate, rollback by step and seeding data) when releasing a Phoenix/OTP application. This script is extended from original [Running Migration documentation](https://hexdocs.pm/distillery/running-migrations.html) of Distillery’s creator, I and my colleagues - we wrote more functions to support database rollback and seeding data.


### Step 1:

- Init a new module

```
# apps/myapp/lib/migration.ex

defmodule MyApp.ReleaseTasks do
  @start_apps [
    :postgrex,
    :ecto
  ]
  @app :myapp

  def repos, do: Application.get_env(@app, :ecto_repos, [])

  def seed do
    prepare()
    # Run seed script
    Enum.each(repos(), &run_seeds_for/1)

    # Signal shutdown
    IO.puts("Success!")
  end

  defp run_seeds_for(repo) do
    # Run the seed script if it exists
    seed_script = seeds_path(repo)

    if File.exists?(seed_script) do
      IO.puts("Running seed script..")
      Code.eval_file(seed_script)
    end
  end

  def migrate do
    prepare()
    Enum.each(repos(), &run_migrations_for/1)
    IO.puts("Migrations successful!")
  end

  defp run_migrations_for(repo) do
    app = Keyword.get(repo.config, :otp_app)
    IO.puts("Running migrations for #{app}")
    Ecto.Migrator.run(repo, migrations_path(repo), :up, all: true)
  end

  def rollback do
    prepare()

    get_step =
      IO.gets("Enter the number of steps: ")
      |> String.trim()
      |> Integer.parse()

    case get_step do
      {int, _trailing} ->
        Enum.each(repos(), fn repo -> run_rollbacks_for(repo, int) end)
        IO.puts("Rollback successful!")

      :error ->
        IO.puts("Invalid integer")
    end
  end

  defp run_rollbacks_for(repo, step) do
    app = Keyword.get(repo.config, :otp_app)
    IO.puts("Running rollbacks for #{app} (STEP=#{step})")
    Ecto.Migrator.run(repo, migrations_path(repo), :down, all: false, step: step)
  end

  defp prepare do
    IO.puts("Loading #{@app}..")
    # Load the code for myapp, but don't start it
    :ok = Application.load(@app)

    IO.puts("Starting dependencies..")
    # Start apps necessary for executing migrations
    Enum.each(@start_apps, &Application.ensure_all_started/1)

    # Start the Repo(s) for myapp
    IO.puts("Starting repos..")
    Enum.each(repos(), & &1.start_link(pool_size: 1))
  end

  defp seeds_path(repo), do: priv_path_for(repo, "seeds.exs")

  defp priv_path_for(repo, filename) do
    app = Keyword.get(repo.config, :otp_app)
    IO.puts("App: #{app}")
    repo_underscore = repo |> Module.split() |> List.last() |> Macro.underscore()
    Path.join([priv_dir(app), repo_underscore, filename])
  end

  defp priv_dir(app), do: "#{:code.priv_dir(app)}"
end
```

### Step 2:

Create shell script files to call functions:

- Migrate script at `rel/commands/migrate.sh`

```
#!/bin/sh

$RELEASE_ROOT_DIR/bin/myapp command Elixir.MyApp.ReleaseTasks migrate
```

- Seed script at `rel/commands/seed.sh`

```
#!/bin/sh

$RELEASE_ROOT_DIR/bin/myapp command Elixir.MyApp.ReleaseTasks seed
```

- Rollback script at `rel/commands/rollback.sh`

```
#!/bin/sh

$RELEASE_ROOT_DIR/bin/myapp command Elixir.MyApp.ReleaseTasks rollback
```

### Step 3:

Setup commands from `rel/config.exs`

```
release :myapp do
  set version: current_version(:myapp)
  set applications: [
    :runtime_tools,
    :misc_random,
    admin: :permanent, graphql: :permanent,
    redis: :permanent,
    session: :permanent,
    myapp: :permanent
  ]

  set commands: [
    "seed": "rel/commands/seed.sh",
    "migrate": "rel/commands/migrate.sh",
    "rollback": "rel/commands/rollback.sh",
  ]
end
```


### Step 4:

Once you have deployed the application, you can run:

- Migrate

```
bin/myapp migrate
```

- Seed

```
bin/myapp seed
```

- Rollback. You can specify how many steps you wanna rollback

```
bin/myapp rollback
```

You guys can download the code from my [gist](https://gist.github.com/vinhnglx/8fa626b71b0dd22063f87de54e935316). Thanks!