Running Ecto migrations in a release
By DevOps on Sun 30 June 2019
inIn a dev or test environment, we execute the mix ecto.migrate
command to run
database migrations. When running from a release, however, the mix command is
not available. Instead, we need to execute Ecto.Migrator.run/4
from code.
We do this by defining a helper function which we can then call from the release startup script:
/srv/foo/current/bin/foo eval "MyApp.ReleaseTasks.migrate.run"
The main point is that we need to initialize the database connection
information the same way as your application normally does, e.g. by loading a
runtime file like /etc/foo/config.toml
or setting environment variables.
If you use TOML configuration files, you will need to install the package
toml_config_provider from Hex.
Following is a working example. Where you see CHANGEME
, use the name of your
project.
defmodule Foo.ReleaseTasks.Migrate do
@moduledoc "Mix task to run Ecto database migrations"
# CHANGEME: Name of app as used by Application.get_env
@app :foo
# CHANGEME: Name of app repo module
@repo_module Foo.Repo
def run(args \\ [])
def run(_args) do
ext_name = @app |> to_string |> String.replace("_", "-")
config_dir = Path.join("/etc", ext_name)
# Read config.exs if present
config_exs = Path.join(config_dir, "config.exs")
app_env =
case File.exists?(config_exs) do
true ->
IO.puts("==> Loading config file #{config_exs}")
Config.Reader.merge([], Config.Reader.read!(config_exs))
_ ->
app_env
end
# Read TOML config if present (requires `toml_config_provider` package)
config_toml = Path.join(config_dir, "config.toml")
app_env =
case File.exists?(config_toml) do
true ->
IO.puts("==> Loading config file #{config_toml}")
TomlConfigProvider.load(config, config_toml)
_ ->
app_env
end
Application.put_env(@app, @repo_module, app_env[@app][@repo_module])
# Start requisite apps
IO.puts("==> Starting applications..")
for app <- [:crypto, :ssl, :postgrex, :ecto, :ecto_sql] do
{:ok, res} = Application.ensure_all_started(app)
IO.puts("==> Started #{app}: #{inspect(res)}")
end
# Start repo
IO.puts("==> Starting repo")
{:ok, _pid} = apply(@repo_module, :start_link, [[pool_size: 2, log: :info, log_sql: true]])
# Run migrations for the repo
IO.puts("==> Running migrations")
priv_dir = Application.app_dir(@app, "priv")
migrations_dir = Path.join([priv_dir, "repo", "migrations"])
opts = [all: true]
config = apply(@repo_module, :config, [])
pool = config[:pool]
if function_exported?(pool, :unboxed_run, 2) do
pool.unboxed_run(@repo_module, fn ->
Ecto.Migrator.run(@repo_module, migrations_dir, :up, opts)
end)
else
Ecto.Migrator.run(@repo_module, migrations_dir, :up, opts)
end
# Shut down
:init.stop()
end
end