Sometimes, it is very useful to read the manual. I was searching for a way to run custom commands for a Phoenix release and found the solution in the manual:

A common need in production systems is to execute custom commands required to set up the production environment. One of such commands is precisely migrating the database. Since we don't have Mix, a build tool, inside releases, which are a production artifact, we need to bring said commands directly into the release.

The phx.gen.release command created the following release.ex file in your project lib/my_app/release.ex, with the following content:

defmodule MyApp.Release do
  @app :my_app

  def migrate do
    load_app()

    for repo <- repos() do
      {:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :up, all: true))
    end
  end

  def rollback(repo, version) do
    load_app()
    {:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :down, to: version))
  end

  defp repos do
    Application.fetch_env!(@app, :ecto_repos)
  end

  defp load_app do
    Application.load(@app)
  end
end

Where you replace the first two lines by your application names.

Now you can assemble a new release with MIX_ENV=prod mix release and you can invoke any code, including the functions in the module above, by calling the eval command:

_build/prod/rel/my_app/bin/my_app eval "MyApp.Release.migrate"

And that's it! If you peek inside the migrate script, you'll see it wraps exactly this invocation.

You can use this approach to create any custom command to run in production. In this case, we used load_app, which calls Application.load/1 to load the current application without starting it. However, you may want to write a custom command that starts the whole application. In such cases, Application.ensure_all_started/1 must be used. Keep in mind, starting the application will start all processes for the current application, including the Phoenix endpoint. This can be circumvented by changing your supervision tree to not start certain children under certain conditions. For example, in the release commands file you could do:

defp start_app do
  load_app()
  Application.put_env(@app, :minimal, true)
  Application.ensure_all_started(@app)
end

And then in your application you check Application.get_env(@app, :minimal) and start only part of the children when it is set.

source