It has been a long time since my initial post on using Distillery to manage Ecto migrations on startup, and I'm super happy that the Elixir core team has worked on making deployments a breeze now.
The absolute simplest way to achieve migrations on startup is now as follows:
- Write the migrator function using this example here
- Add a startup script in the release overlays folder
- Add the startup script to your dockerfile CMD
Writing the Migrator Functionโ
This is going to be largely lifted from the Phoenix documentation, but the core aspects that you need are all here:
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
defp repos do
Application.fetch_env!(@app, :ecto_repos)
end
defp load_app do
Application.load(@app)
end
end
I have left out the rollback/2
function, but you can include it in if you really think that you will need it. Its more likely that in reality you'll just add in a new migration to fix a bad migration, so it is up to personal preference.
Adding the Startup Scriptโ
With Elixir releases, we now have a nice convenient way to copy our files into our releases automatically, without having to do multiple COPY
commands in our dockerfiles. Neat! Anything to save a few lines of code!
Create your startup file here:
# rel/overlays/startup.sh
./my_app eval "MyApp.Release.migrate"
# optionally, start the app
./my_app start
This assumes that we will be setting our working directory to our release root, which we will do in our docker file.
If you wish to couple the migrations together with the app startup, you can add the optional ./my_app start
portion. However, you can also decouple it so that you don't end up in a bootloop in the event of a bad migration. As always, it really depends on your sitation.
And then in your release configuration:
# mix.exs
releases: [
my_app: [
include_executables_for: [:unix],
# the important part!
overlays: ["rel/overlays"],
applications: [my_app: :permanent]
]
]
This will then copy your files under the re/overlays
directory over to the built release.
Add the Startup Script to Dockerfileโ
Let's run the startup script now from our dockerfile as so:
WORKDIR ".../my_app/bin"
CMD ["./startup.sh"]
The three dots are for illustration purposes, adjust the WORKDIR
to the actual directory that you copied your release binaries to. If you coupled your app startup together with the startup script, the above CMD
will run the migrations and then start up the app.
If you wish to decouple the migrations from the startup, you can do the following:
WORKDIR "..."
CMD ["./startup.sh", "&&", "./my_app", "start" ]
Wrap Upโ
An important mention is that Phoenix now comes with mix phx.gen.release
which also comes with a dockerfile option for bootstrapping docker-based release workflows. The migration files are also automatically generated for you too. However, you wouldn't want to use the helper if you aren't doing any Phoenix stuff, and the above example walkthrough will work for any generic Elixir release.
Thanks for reading!