I’ve spent some time understanding how to do hot code reloading with releases built using mix release
, and here I’d like to detail the steps needed, in hopes that it will help someone.
So, in order to make hot-reloadable release you need:
- Write an appup file (the Erlang’s Appup cookbook is a great resource for that: Appup Cookbook — Erlang System Documentation v27.0.1).
- Tweak the release generated by
mix release
, by copying the .rel & .appup files into expected places and generating the relup file. This can be done by hand, but I’ve wrote a simple function that can do that right inside themix release
task:
def project do
[
releases: [
app_name: [
include_executables_for: [:unix],
steps: [:assemble, &release_fixup_step/1, :tar],
],
],
...
]
end
defp release_fixup_step(release) do
releases_dir = Path.join(release.path, "releases")
rel_file = Path.join([releases_dir, release.version, "#{release.name}.rel"])
:ok = :release_handler.create_RELEASES(releases_dir, rel_file, [])
# Yes, we have three copies of a same file in the same place.
# This is necessary to appease the release_handler.
File.cp!(rel_file, Path.join([releases_dir, release.version, "#{release.name}-#{release.version}.rel"]))
File.cp!(rel_file, Path.join([releases_dir, "#{release.name}-#{release.version}.rel"]))
# Copy appup file into the correct location and generate the relup.
appup_file = Path.join("appups", "#{release.version}.appup")
if File.exists?(Path.join("appups", "#{release.version}.appup")) do
File.cp!(
appup_file,
Path.join([release.path, "lib", "#{release.name}-#{release.version}", "ebin", "#{release.name}.appup"])
)
{:ok, appup} = :file.consult(appup_file)
[{appup_release_version, [{version_up_from, _}], [{version_down_to, _}]}] = appup
if appup_release_version != to_charlist(release.version) do
raise "Unexpected version in appup: #{appup_release_version} (expected #{release.version})"
end
if version_up_from != version_down_to do
raise "Unexpected versions in appup instructions: #{version_up_from} != #{version_down_to}"
end
:systools.make_relup(
~c"#{release.name}-#{release.version}",
[~c"#{release.name}-#{version_up_from}"],
[~c"#{release.name}-#{version_down_to}"],
path: [to_charlist(Path.join(release.path, "releases/*")), to_charlist(Path.join(release.path, "lib/*/ebin"))],
outdir: to_charlist(release.version_path)
)
end
release
end
Now, after running mix release
you will get a release archive in _build/<env>/app_name-<version>.tar.gz
.
- Copy this archive into the
releases/
directory of your running release. - Connect to the Iex shell of the running release.
- Carefully run the commands:
:release_handler.unpack_release(~c"app_name-<version>")
:release_handler.install_release(~c"<version>")
:release_handler.make_permanent(~c"<version>")