Updating Hex.pm to use Elixir releases

Elixir v1.9 will ship with releases support and in this blog post we want to show how we have used this exciting new feature on the Hex.pm project.

Installing Elixir master

(Update: This section is no longer relevant since v1.9 is already out!)

Since Elixir v1.9 is not out yet, we need to use the development version. Locally, my preferred approach is to use the Elixir plugin for the asdf-vm version manager.

Here’s a couple of ways we may use asdf to install recent development versions:

# install latest master
$ asdf install elixir master
$ asdf local elixir master

# or, install particular revision:
$ asdf install elixir ref:b8b7e5a
$ asdf local elixir ref:b8b7e5a

Per “Deployment” section of mix release documentation:

A release is built on a host, a machine which contains Erlang, Elixir, and any other dependencies needed to compile your application. A release is then deployed to a target, potentially the same machine as the host, but usually separate, and often there are many targets (either multiple instances, or the release is deployed to heterogeneous environments).

We deploy Hex.pm using Docker containers and we needed to change our Dockerfile. If you’re deploying using buildpacks (e.g. to Heroku or Gigalixir), it should be as simple as setting elixir_version=master in your elixir_buildpack.config.

Setting up releases

Elixir 1.9 ships with two new Mix tasks to work with releases:

  • mix release.init - generates sample files for releases
  • mix release - builds the release The sample files generated by mix release.init are optional, if they are not present in your project then the release will be built with default options.

On Hex.pm, previously we were building releases using Distillery and to work with Elixir releases we needed to make a few small tweaks. Here are the main ones:

  • add :releases section to mix.exs - this is an optional step but since we don’t deploy on Windows, we only need to generate executable files for UNIX-like systems
  • replace rel/vm.args with rel/vm.args.eex
  • replace rel/hooks/pre_configure with rel/env.sh.eex
  • add config/releases.exs for runtime configuration of the release
  • remove Distillery dependency (remember to mix deps.unlock it!) See the “Replace Distillery with Elixir releases” PR on Hex.pm repo for more details.

We now have a few files that deal with configuring our app/release, let’s take a step back and see what they can do:

  • config/prod.exs - provides build-time application configuration
  • config/releases.exs - provides runtime application configuration. We’re using the new Config module and the System.fetch_env!/1 function, also introduced in Elixir v1.9.0, to conveniently return the environment variable if set, or raise an error.
  • rel/vm.args.eex - provides a static mechanism for configuring the Erlang Virtual Machine and other runtime flags. For now, we use the defaults but if down the line we’d tune the VM, we’d set the options here.
  • rel/env.sh.eex - provides a dynamic mechanism for setting up the VM, runtime flags, and environment variables. RELEASE_NODE and RELEASE_COOKIE variables are used by the release script, see “Environment variables” section in the documentation for all recognized variables. The POD_A_RECORD variable we have there is specific to our deployment environment on Hex.pm, we deploy it to Google Kubernetes Engine.

See “Application configuration” and “vm.args and env.sh (env.bat)” sections for more information.

Finally, we use the mix release task to actually assemble the release:

$ mix release
* assembling hexpm-0.0.1 on MIX_ENV=dev
* using config/releases.exs to configure the release at runtime
* creating _build/dev/rel/hexpm/releases/0.0.1/vm.args
* creating _build/dev/rel/hexpm/releases/0.0.1/env.sh
Release created at _build/dev/rel/hexpm!
# To start your system
_build/dev/rel/hexpm/bin/hexpm start
Once the release is running:
# To connect to it remotely
_build/dev/rel/hexpm/bin/hexpm remote
# To stop it gracefully (you may also send SIGINT/SIGTERM)
_build/dev/rel/hexpm/bin/hexpm stop
To list all commands:
_build/dev/rel/hexpm/bin/hexpm

Running the release

The generated release script (bin/hexpm) has many commands:

$ _build/dev/rel/hexpm/bin/hexpm
Usage: hexpm COMMAND [ARGS]
The known commands are:
start          Starts the system
start_iex      Starts the system with IEx attached
daemon         Starts the system as a daemon
daemon_iex     Starts the system as a daemon with IEx attached
eval "EXPR"    Executes the given expression on a new, non-booted system
rpc "EXPR"     Executes the given expression remotely on the running system
remote         Connects to the running system via a remote shell
restart        Restarts the running system via a remote command
stop           Stops the running system via a remote command
pid            Prints the OS PID of the running system via a remote command
version        Prints the release name and version to be booted

In our Hex.pm deployment we have used two of these commands for now:

  • bin/hexpm start - we use it as the start command to be run in our Docker container
  • bin/hexpm eval - we use it to run DB migrations and other maintenance scripts. For migrations, the command is: bin/hexpm eval 'Hexpm.ReleaseTasks.migrate()'.

Summary

In this blog post we’ve walked through using Elixir releases on an existing project, Hex.pm. We’ve installed the development version of Elixir, configured the release, and adjusted our deployment setup to use it. Hex.pm was previously using Distillery, and with minimal changes we were able to update it to use built-in releases support.

Overall, I’m very happy about this change. We’ve ended up with about the same amount of configuration code, but I think it’s a little bit better structured and more obvious.

I especially like new conventions around configuration. Where previously we used workarounds like config :app, {:system, "ENV_VAR"} and "${ENV_VAR}" (and REPLACE_OS_VARS=true), we now have a clear distinction between build-time and runtime configuration. mix release documentation does a really good job of explaining configuration aspects in particular but also the whole release process in general.

Building the release is now faster too, on my machine ~2.5s now vs ~5.5s before. Granted, it’s probably the least concern but it’s a nice cherry on top nonetheless.

As of this writing, Hex.pm is already deployed using Elixir releases. Now your turn - try out releases on your project! (And if something goes wrong, submit an issue!)

P.S.: This post was originally published on Plataformatec’s blog.