Using the New Elixir 1.9 Releases Feature
Elixir 1.9 was released today, and with it comes first-party support for releases. Previously, creating a release for your Elixir application was done using Distillery; but the functionality is now present in Elixir itself.
In this post, I'll make an example application in the style of Elixir 1.8, before releases, using Distillery. I will then convert the application to use Elixir 1.9's releases feature and see how it compares.
The Application
The application used for testing is going to be a simple TCP echo server.
First we create a new project:
$ mix new --sup echo
Delete lib/echo.ex and replace it with the following server.ex
defmodule Echo.Server do
use Task
def start_link(port) do
{:ok, socket} = :gen_tcp.listen(port, [:binary, active: false])
listen(socket)
end
defp listen(socket) do
with {:ok, conn} <- :gen_tcp.accept(socket)
do spawn(fn -> handle(conn) end)
else err -> err
end
listen(socket)
end
defp handle(conn) do
case :gen_tcp.recv(conn, 0) do
{:ok, data} ->
:gen_tcp.send(conn, data)
handle(conn)
_ ->
:ok
end
end
end
And to lib/echo/application.ex, add {Echo.Server, Application.get_env(:echo, :listen_port, 8080)} to the children list in the start function so that our echo server is started on a mix run.
Before we set up Distillery, let's test the application.
$ mix run
Compiling 2 files (.ex)
$ nc localhost 8080
foo
foo
bar
bar
^C
Great!
Distillery Releases
We're in the world of Elixir 1.8 still, so "releases" means "Distillery".
Let's use Distillery by adding {:distillery, "~> 2.0"} to the deps function in mix.exs, running mix do deps.get, compile, then mix release.init, and finally MIX_ENV=prod mix release.
If all goes well, there will be a tarball at _build/prod/rel/echo/releases/0.1.0/echo.tar.gz that can be used for deployments.
Next, let's switch to Elixir 1.9, and see what's changed.
Elixir 1.9 Releases
Now that I've upgraded my Elixir runtime to 1.9, I need to update the application as well. This could be done by creating a new project and copying over lib, but instead I just deleted everything in the project except for lib and mix.exs, removed the Distillery dependency from mix.exs, and changed the Elixir version in mix.exs to 1.9.
Now, I have yet to see any documentation on the new Elixir release feature, but I think it's likely that it works about the same as Distillery did. Now that we no longer have Distillery installed as a dependency, let's try releasing again and see what happens:
$ MIX_ENV=prod mix release
Compiling 2 files (.ex)
Generated echo app
[...]
Release created at _build/prod/rel/echo!
[...]
Neat. So far seems the same as using Distillery, minus the setup.
Differences
Let's look at the 1.9 release artifacts and see how they match up to the ones generated by Distillery by checking out _build/prod/rel/echo/releases/0.1.0/.
$ ls _build/prod/rel/echo/releases/0.1.0/
consolidated elixir env.sh start.boot start_clean.boot sys.config
echo.rel env.bat iex start.script start_clean.script vm.args
The only noticeable thing is that there's no .tar.gz file. Seems like we can create one by just tar'ing this directory ourselves if we wanted to.
Personally, I think I like this approach of not generating a tarball better, because it makes everything slightly more suitable for a release using rsync or something else that doesn't mind transfering multiple files. We can of course still use a tarball based distribution method by simply adding a tar step to the deployment procedure.
All in all, it seems like Elixir releases have just integrated Distillery's functionality directly into Mix. Migrating should require very little fanfare past "don't bother installing Distillery" and "tar your release if you want".
Bonus: Runtime Configuration
We didn't create any config files in the example app and instead decided to rely on defaults. If we had wanted to allow overriding the configuration using environment variables, Distillery releases would not have helped us much because it does all its release configuration at compile-time. We would have needed to use some package that implements a Distillery ConfigProvider to modify config at runtime.
Elixir's new releases feature makes this easy with config/releases.exs, made possible by the new Config module in Elixir 1.9. Because Mix isn't available at runtime, anything using Mix.Config needs to be evaluated at compile time. Now that we have Config, we can write the following config/releases.exs to add environment-variable-based runtime configuration to our application
import Config
config :echo,
listen_port: (System.get_env("PORT") || "8080") |> String.to_integer
Now if we create a new release and run the application again with an environment variable set, the environment variable will override the default configuration.
$ mix release
* using config/releases.exs to configure the release at runtime
[...]
$ PORT=8040 _build/dev/rel/echo/bin/echo start
$ nc localhost 8040
foo
foo
bar
bar
^C