Elixir by Example

Releases

Every command ran when the site was built; the output is real Download project

So far every program ran with elixir file.exs or mix run. That needs Elixir, Mix and your source code on the machine, compiles on start, and gives you no standard way to start the app in the background, connect to it, or stop it. Servers want something else: one directory that holds the compiled app, its dependencies and the Erlang runtime, plus a script to operate it. That directory is a release, and mix release builds it.

On this page:

  1. A tiny Mix project, greeter: an application that starts a counter.
  2. Build a release of it and look inside.
  3. Operate it with the release's script: run a one-off command, start it in the background, call into the running app, and stop it.

The project

An ordinary Mix project. Nothing in it is specific to releases: mix release works with any application.

mix.exs
defmodule Greeter.MixProject do
  use Mix.Project

  def project do
    [app: :greeter, version: "0.1.0", elixir: "~> 1.20", deps: []]
  end

  def application do
    [mod: {Greeter.Application, []}]
  end
end

The application starts one process, a counter, under a supervisor.

lib/greeter/application.ex
defmodule Greeter.Application do
  use Application

  @impl true
  def start(_type, _args) do
    Supervisor.start_link([Greeter.Counter], strategy: :one_for_one, name: Greeter.Supervisor)
  end
end
lib/greeter/counter.ex
defmodule Greeter.Counter do
  use Agent

  def start_link(_opts), do: Agent.start_link(fn -> 0 end, name: __MODULE__)

  def next, do: Agent.get_and_update(__MODULE__, &{&1 + 1, &1 + 1})
end

Build the release

MIX_ENV=prod compiles with production settings; releases are almost always built that way.

$ MIX_ENV=prod mix release
Compiling 2 files (.ex)
Generated greeter app
* assembling greeter-0.1.0 on MIX_ENV=prod
* skipping runtime configuration (config/runtime.exs not found)

Release created at _build/prod/rel/greeter

    # To start your system
    _build/prod/rel/greeter/bin/greeter start

Once the release is running:

    # To connect to it remotely
    _build/prod/rel/greeter/bin/greeter remote

    # To stop it gracefully (you may also send SIGINT/SIGTERM)
    _build/prod/rel/greeter/bin/greeter stop

To list all commands:

    _build/prod/rel/greeter/bin/greeter

The release lands in _build/prod/rel/greeter. bin holds the script you operate it with, lib the compiled code of the app and of every application it needs (Elixir's own included), releases the boot files and configuration, and erts-* the Erlang runtime itself. The server needs none of Elixir, Mix or the source.

$ ls _build/prod/rel/greeter
$ ls _build/prod/rel/greeter/lib
bin
erts-17.1.1
lib
releases
compiler-10.0.7
elixir-1.20.5
greeter-0.1.0
iex-1.20.5
kernel-11.0.4
sasl-4.4
stdlib-8.1.1

Operate it

eval runs one expression with the release's code loaded but the application not started: handy for database migrations and other one-off tasks.

$ _build/prod/rel/greeter/bin/greeter eval 'IO.inspect(Code.ensure_loaded?(Greeter.Counter))'
$ _build/prod/rel/greeter/bin/greeter eval 'IO.inspect(Process.whereis(Greeter.Counter))'
true
nil

daemon starts the app in the background (start keeps it in the foreground, which is what you want under systemd or in a container). pid succeeds once the node is up, so we wait for it.

$ _build/prod/rel/greeter/bin/greeter daemon
$ until _build/prod/rel/greeter/bin/greeter pid > /dev/null 2>&1; do sleep 0.1; done

rpc runs an expression inside the running app, over Erlang distribution. Every call reaches the same counter, so the count keeps going up.

$ for i in 1 2 3; do
  _build/prod/rel/greeter/bin/greeter rpc 'IO.puts(Greeter.Counter.next())'
done
1
2
3

stop shuts the app down cleanly: supervisors stop their children in order, then the VM exits.

$ _build/prod/rel/greeter/bin/greeter stop

Pitfall: a release contains the Erlang runtime and native code compiled for the machine that built it. A release built on a Mac won't run on Linux. Build on the same OS, CPU architecture and libc as the servers, which is the main reason to build inside a container.

Pitfall: rpc and remote connect with the cookie in releases/COOKIE, generated when the release is built. Every node of a cluster needs the same cookie: set RELEASE_COOKIE in the environment rather than relying on that file.

Pitfall: bin/greeter remote opens an IEx shell inside the running app. It's the most powerful debugging tool you have and just as dangerous: any function you call runs in production.