Exrm is a great tool for building releases for Elixir applications so you can deploy them on the web and even in an embedded hardware.
We have been using Exrm here at Plataformatec. It is the chosen tool for deploying our projects and the contributors are doing a great job maintaining and developing new features. However, since Exrm is in its early days, it’s natural that there are some features that aren’t completely covered yet. One of these features was the possibility to run migration tasks on a release.
An Exrm release is a package with binaries, dependencies and tools so you can run and manage it independently. All needed dependencies are compiled within the releases. Even Erlang and Elixir binaries are compiled within the release, so we don’t need to install it in the machine. It’s a self-contained package! As we don’t have the source code included in our release, we can’t run Mix tasks because it needs a
mix.exs file.
To run our migrations without Mix, an approach is using the Exrm console. There you can call Ecto Migrator.
However, since the 1.0.3 version, we have a
command task available for Exrm releases. With this interface, we can call a module defined within our Elixir application.
Example:
rel/my_app/bin/my_app command <Module> <function> <args>
How does it work?
Well, under the hood, what this task does is basically the same we needed to do before. It opens a console and runs the code we’ve sent. Although, it runs an Erlang console (through the
erlexec binary contained in the release).
To the migration!
We’ve defined an Elixir module in our app and a
migrate function:
defmodule Release.Tasks do def migrate do {:ok, _} = Application.ensure_all_started(:my_app) path = Application.app_dir(:my_app, "priv/repo/migrations") Ecto.Migrator.run(MyApp.Repo, path, :up, all: true) :init.stop() end end
Notice that in order to run the migrations, we’ll need to ensure that our app or the supervisor tree that Ecto counts in. In this example, we’ve just ensured that the entire app has started, but you could get only
ecto,
postgrex and
logger. Also, we need to call
:init.stop in order to close the console, otherwise our buffer will remain opened, waiting for something.
To call this function we’ll need to:
rel/my_app/bin/my_app command Elixir.Release.Tasks migrate
We need to include the
Elixir namespace because we’re running it in an Erlang console, as explained above. Otherwise, we can’t reach the module.
Another possible approach is defining an Erlang module instead of Elixir’s:
defmodule :release_tasks do def migrate do {:ok, _} = Application.ensure_all_started(:my_app) path = Application.app_dir(:my_app, "priv/repo/migrations") Ecto.Migrator.run(MyApp.Repo, path, :up, all: true) :init.stop() end end
This way our call gets a bit prettier:
rel/my_app/bin/my_app command release_tasks migrate
Conclusion
Since Elixir’s early days, several people are contributing and developing great tools. We’re certain that this is one of the reasons that makes Elixir so attractive. And because of this, things are getting better for us in Elixir when dealing with releases and deployments.
This blog post is highly inspired by an Exrm issue discussion. Make sure to read or cooperate with it if you feel like it. There are plenty of great tips in it and they’ll probably have tons more as this idea grows.
How are you currently dealing with these tasks? Are there any tips to share?
This is great. No longer need a Docker container. 🙂
It throws the following error for me
{“init terminating in do_boot”,{undef…
Can you post the full error? There is not much we can do with only the information you posted. 🙂
—
*José Valim Founder and Director of R&D*
Oh, I found the reason
I replaced all “Application” parts with my application name. But the only thing to replace was the “my_app” and “MyApp” part
Where?