DEV Community

Vincent Nguyen
Vincent Nguyen

Posted on

4

Migration scripts (migrate/rollback/seeding) database when releasing a Phoenix/OTP app by Distillery

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 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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
  • Seed script at rel/commands/seed.sh
#!/bin/sh

$RELEASE_ROOT_DIR/bin/myapp command Elixir.MyApp.ReleaseTasks seed
Enter fullscreen mode Exit fullscreen mode
  • Rollback script at rel/commands/rollback.sh
#!/bin/sh

$RELEASE_ROOT_DIR/bin/myapp command Elixir.MyApp.ReleaseTasks rollback
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Step 4:

Once you have deployed the application, you can run:

  • Migrate
bin/myapp migrate
Enter fullscreen mode Exit fullscreen mode
  • Seed
bin/myapp seed
Enter fullscreen mode Exit fullscreen mode
  • Rollback. You can specify how many steps you wanna rollback
bin/myapp rollback
Enter fullscreen mode Exit fullscreen mode

You guys can download the code from my gist. Thanks!

Sentry image

Hands-on debugging session: instrument, monitor, and fix

Join Lazar for a hands-on session where you’ll build it, break it, debug it, and fix it. You’ll set up Sentry, track errors, use Session Replay and Tracing, and leverage some good ol’ AI to find and fix issues fast.

RSVP here →

Top comments (0)

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay