Phoenix Database Migrations with Elixir Releases

When you deploy your Phoenix app with Elixir releases, you need to create a new module to handle database migrations and other custom commands. You can’t run mix ecto.migrate and other Mix commands when using releases because Mix is not available. We’re going to review the default Release module provided in the Phoenix guides to run migrations in your production environment and then add additional commands and functions to handle some other less common situations.

Overview

We’ll cover the following topics and steps as we learn about database migrations and other custom commands for Elixir applications.

  1. Create new Elixir Phoenix App
  2. Add Release file for database migrations
  3. Update for SSL connections
  4. Add function for database seeds

Prerequisites

To follow this guide, make sure you have at least the following applications installed on your machine. In the brackets are the versions with which this guide was written.

  • Elixir (1.12.3-otp-24)
  • Erlang (OTP 24.1)
  • Phoenix (1.6.2)
  • Node.js with npm (15.14.0)

If you run into problems, the finished project is available on GitHub as StakNine HelloMigrations.

Create new Elixir Phoenix App

We are going to create a simple Phoenix project called HelloMigrations. Use the command line to update your Phoenix version and create a new Phoenix project.

mix archive.install hex phx_new 1.6.2
mix phx.new hello_migrations

Change into the project directory, create your database, and start your server

cd hello_migrations
mix ecto.create

We don’t plan to add a function to create a database because you will usually have some external way to deploy your database in production. We’ll use mix to create it in this guide.

Now visit http://localhost:4000 and you should see the Welcome to Phoenix! page.

phoenix homepage

Next we want to add users to your Phoenix application with the phx.gen html task.

mix phx.gen.html Accounts User users name:string email:string \
bio:string number_of_pets:integer

Follow the directions to add the resource to the lib/hello_migrations_web/router.ex file.

Note: Hold off on running your database migrations using mix ecto.migrate for now. We’ll migrate the database later using the release command.

Next, if you are using Phoenix v1.6, we only need to make one update to config to use releases. Uncomment or add the following line in your config/runtime.exs configuration file.

config :hello_migrations, HelloMigrationsWeb.Endpoint, server: true

Finally export the following environment variables. First use mix phx.gen.secret to generate a string for your SECRET_KEY_BASE environment variable. Then add the connection string for your DATABASE_URL environment variable.

# generate a really long secret
mix phx.gen.secret

export SECRET_KEY_BASE=REALLY_LONG_SECRET
export DATABASE_URL=postgres://postgres:postgres@localhost:5432/hello_migrations_dev

Now we’re ready to add the ability to migrate our database with releases.

Add Release file for database migrations

For this section, we will use the recommended file from the Phoenix Deploying With Releases guide.

When a release is assembled, Mix is no longer available inside a release. As a result, you can’t use any of the Mix tasks in your Elixir application. However, you still need a way to migrate your database.

Release file

Add the following file to your project at lib/hello_migrations/release.ex.

defmodule HelloMigrations.Release do
  @app :hello_migrations

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

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

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

The example above uses Ecto.Migrator.with_repo/3 to make sure the repository is started and then runs all migrations up or a given migration down. Note you will have to replace HelloMigrations and :hello_migrations on the first two lines in your project.

Load dependencies and compile

Now we’ll load dependencies to compile code and assets. Note: mix assets.deploy is for Phoenix v1.6 and later.

mix deps.get --only prod
MIX_ENV=prod mix compile
MIX_ENV=prod mix assets.deploy

# Output of command to compile assets
  ../priv/static/assets/app.js   79.1kb
  ../priv/static/assets/app.css  10.8kb

⚡ Done in 15ms
Check your digested files at "priv/static"

Deploy Your Elixir Release Locally

Once you’ve completed those sections, you should be able to test your app and start building releases.

Run the mix command to build the release with the MIX_ENV=prod mix release build script.

MIX_ENV=prod mix release

# Output
* assembling hello_migrations-0.1.0 on MIX_ENV=prod
* using config/runtime.exs to configure the release at runtime

Release created at _build/prod/rel/hello_migrations!

    # To start your system
    _build/prod/rel/hello_migrations/bin/hello_migrations start

Once the release is running:

    # To connect to it remotely
    _build/prod/rel/hello_migrations/bin/hello_migrations remote

    # To stop it gracefully (you may also send SIGINT/SIGTERM)
    _build/prod/rel/hello_migrations/bin/hello_migrations stop

To list all commands:

    _build/prod/rel/hello_migrations/bin/hello_migrations

Migrate your database

_build/prod/rel/hello_migrations/bin/hello_migrations eval "HelloMigrations.Release.migrate"

# Output
19:37:51.517 [info] == Running 20211103022310 HelloMigrations.Repo.Migrations.CreateUsers.change/0 forward
19:37:51.518 [info] create table users
19:37:51.524 [info] == Migrated 20211103022310 in 0.0s

And start your server

_build/prod/rel/hello_migrations/bin/hello_migrations start

Now you should be able to visit http://localhost:4000/users on your local machine to see the index of users. You can add a user to verify the database was migrated properly.

Add user after database migration

Update for SSL connections

Many cloud hosting providers use SSL for database connections. A new Phoenix project will generate the the following config for your repo. If your database will connect via SSL, then you’ll need to uncomment ssl: true.

  config :hello_migrations, HelloMigrations.Repo,
    # ssl: true,
    # socket_options: [:inet6],
    url: database_url,
    pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10")

If you are using releases, you’ll need to start the ssl application before you run your migrations in your Release.ex file. Otherwise you’ll get an error similar to this:

** (RuntimeError) SSL connection can not be established because `:ssl` application is not started

Here is an updated Release.ex file with Application.ensure_all_started/1 added to each function with the private function ensure_started/0.

defmodule HelloMigrations.Release do
  @app :hello_migrations

  def migrate do
    ensure_started()

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

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

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

  defp ensure_started do
    Application.ensure_all_started(:ssl)
  end
end

The migration file is run by the eval command without the normal application startup process.

_build/prod/rel/hello_migrations/bin/hello_migrations eval "HelloMigrations.Release.migrate"

As a result, it doesn’t load or start what is defined in mix.exs. As you can see in the code, it loads your application manually, and it uses Ecto.Migrator.with_repo/3 to manually start the Ecto repo(s).

Application.ensure_all_started/1 will start your entire application, so since it’s a Phoenix app it will start the whole server and any other child processes you have specified in your supervisor.

You don’t need the entire application when running a migration, so use Application.ensure_started/1 to selectively start only the ssl application, including the apps it needs. You need to add Application.ensure_started(:ssl) at the top of your migration and rollback functions.

Add function for database seeds

Another common mix task is to run database seeds. We’re going to add a user with the priv/repo/seeds.exs file generated by Phoenix. Uncomment the sample script and add the User data.

# Script for populating the database. You can run it as:
#
#     mix run priv/repo/seeds.exs
#
# Inside the script, you can read and write to any of your
# repositories directly:
#
HelloMigrations.Repo.insert!(%HelloMigrations.Accounts.User{
  email: "[email protected]",
  name: "User1",
  bio: "The start to my bio",
  number_of_pets: 2
})
#
# We recommend using the bang functions (`insert!`, `update!`
# and so on) as they will fail if something goes wrong.

Update Release module

Now we are going to use Code.eval_file/1 to execute the seeds.exs file. Add the following function to lib/hello_migrations/release.ex.

defmodule HelloMigrations.Release do
  @app :hello_migrations

# other functions

  def seeds do
    Application.load(@app)

    {:ok, _, _} =
      Ecto.Migrator.with_repo(HelloMigrations.Repo, fn _repo ->
        Code.eval_file("priv/repo/seeds.exs")
      end)
  end
end

Build release and load seeds

Then recompile your release, run your seeds function, and restart your app.

MIX_ENV=prod mix release
_build/prod/rel/hello_migrations/bin/hello_migrations eval "HelloMigrations.Release.seeds"
_build/prod/rel/hello_migrations/bin/hello_migrations start

Now revisit your index of users at http://localhost:4000/users and you should see the user you added via the seed function.

User index after database seeds

There are many other approaches to loading database seeds in your Phoenix application. Check out this Elixir Forum thread for some other options.

Conclusion

We used a sample application to learn more about how to run database migrations when using Elixir releases. We also learned how to set up your project to use SSL to connect to your database in production. Finally, we learned how to add our own custom commands to the module to accomplish other tasks in your Elixir application like loading database seeds.

If you liked learning about database migrations, we have an entire book about managing your app in production. See below for how to get the Quick Reference Guide from the Phoenix Deployment Handbook for free.

Want To Know How To Deploy Phoenix Apps Using A single Command?

This brand-new FREE training reveals the most powerful new way to reduce your deployment time and skyrocket your productivity… and truly see your programming career explode off the charts!