We're planting a tree for every job application! Click here to learn more

Elixir release with Distillery

Bruno Ripa

4 Jun 2019

4 min read

Elixir release with Distillery
  • Elixir

After you have spent time to develop your Elixir application, you have the challenge of deploying it. I call it a challenge because, without taking into account complex scenarios (scalability, reliability and so on), you will be pretty soon bumping into some unexpected behaviours; this happens because of the difference between build time and run time environment.

Build time vs run time

During development you run your application by launching

$ mix phx.server

and probably you have a set of exported environment variables that are used by your application, in config|dev|..|.exs file, like this:

config :app,
    payments_key: System.get_env("PAYMENTS_KEY")

which could be the key of some remote payment service your app is using. As long as you are using mix phx.server you are de facto running a mix application, and this solution will work.

If you, instead, create a production build and run it, properly exporting environment variables would not just work.

This happens because the System.get_env(..) instruction is evaluated at compile time, and so, providing the values after such step would cause an empty value to be found instead of the expected one.

Imagine we have a controller that loads the values of a given application key:

{% gist 3737b82a9a3addc88f23c84e747c7e23 %}

served at /api/keys; by adding the following configuration in config.exs (so in the base file, extended by [dev|test|prod].exs files) and running the app you'll see:

$ PAYMENT_KEY=pk mix phx.server
Compiling 11 files (.ex)
Generated app app
[info] Running ElixirTestOneWeb.Endpoint with cowboy 2.6.3 at 0.0.0.0:8080 (http)
[info] Access ElixirTestOneWeb.Endpoint at http://localhost:8080

$ curl http://localhost:8080/api/keys | jq
% Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                Dload  Upload   Total   Spent    Left  Speed
100    29  100    29    0     0    475      0 --:--:-- --:--:-- --:--:--   483
{
    "keys": {
        "payment_key": "pk"
    }
}

And it will work for all the other envs (try running MIX_ENV=prod PAYMENT_KEY=pk mix phx.server and verify).

Release creation

Let's try what happens if we create a release build:

# Needed to generate the release configuration file
$ MIX_ENV=prod mix release.init

An example config file has been placed in rel/config.exs, review it,
make edits as needed/desired, and then run `mix release` to build the release

$ MIX_ENV=prod mix release --env=prod 
==> Assembling release..
==> Building release app:0.1.0 using environment prod
==> Including ERTS 10.3.1 from /usr/local/Cellar/erlang/21.3.2/lib/erlang/erts-10.3.1
==> Packaging release..
Release successfully built!
To start the release you have built, you can use one of the following tasks:

    # start a shell, like 'iex -S mix'
    > _build/prod/rel/app/bin/app console

    # start in the foreground, like 'mix run --no-halt'
    > _build/prod/rel/app/bin/app foreground

    # start in the background, must be stopped with the 'stop' command
    > _build/prod/rel/app/bin/app start

If you started a release elsewhere, and wish to connect to it:

    # connects a local shell to the running node
    > _build/prod/rel/app/bin/app remote_console

    # connects directly to the running node's console
    > _build/prod/rel/app/bin/app attach

For a complete listing of commands and their use:

    > _build/prod/rel/app/bin/app help

and run it as suggested by the resulting output:

$ PAYMENT_KEY=pk HOSTNAME=localhost PORT=8080 _build/prod/rel/app/bin/app foreground

Trying to hit the endpoint for the values will generate the following result:

$ curl http://localhost:8080/api/keys | jq  
% Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                               Dload  Upload   Total   Spent    Left  Speed
100    29  100    29    0     0   4443      0 --:--:-- --:--:-- --:--:--  4833
{
    "keys": {
        "payment_key": null
    }
}

This happens because res = Application.get_env(:app, :config_keys) is trying to access a value that has been evaluated at build time, while we provided (correctly) at run time.

Distillery config providers

The proper way to handle this is by using Distillery Config Providers: long story short, it's a way to inject configuration that will be evaluated at run time, so that System.get_env commands will be correctly valued.

It's a very easy solution, because all it takes is to prepare a proper configuration file to be used during the release build.

The first step is to create the release file with MIX_ENV=prod mix release.init. This will create a file config.exs that contains information about environments and releases (info). In order to inject the proper configuration provider, you need to add, in the prod environment, the following config lines:

set(
    config_providers: [
        {Mix.Releases.Config.Providers.Elixir, ["${RELEASE_ROOT_DIR}/config/runtime.exs"]}
    ]
)

set(
    overlays: [
        {:copy, "config/runtime.exs", "config/runtime.exs"}
    ]
)

This tells the system to make two things: to copy config/prod.exs as ${RELEASE_ROOT_DIR}/config/runtime.exs and use it as configuration file.

So, if we now build the app, everything will work fine:

$ ~/dev/elixir/gcp_article/app(master*) » MIX_ENV=prod mix release --env=prod
==> Assembling release..
==> Building release app:0.1.0 using environment prod
==> Including ERTS 10.3.1 from /usr/local/Cellar/erlang/21.3.2/lib/erlang/erts-10.3.1
==> Packaging release..
Release successfully built!
To start the release you have built, you can use one of the following tasks:

    # start a shell, like 'iex -S mix'
    > _build/prod/rel/app/bin/app console

    # start in the foreground, like 'mix run --no-halt'
    > _build/prod/rel/app/bin/app foreground

    # start in the background, must be stopped with the 'stop' command
    > _build/prod/rel/app/bin/app start

If you started a release elsewhere, and wish to connect to it:

    # connects a local shell to the running node
    > _build/prod/rel/app/bin/app remote_console

    # connects directly to the running node's console
    > _build/prod/rel/app/bin/app attach

For a complete listing of commands and their use:

    > _build/prod/rel/app/bin/app help

$ ~/dev/elixir/gcp_article/app(master*) » PAYMENT_KEY=pk HOSTNAME=localhost PORT=8080 _build/prod/rel/app/bin/app foreground
13:36:53.482 [info] Running ElixirTestOneWeb.Endpoint with cowboy 2.6.3 at 0.0.0.0:8080 (http)
13:36:53.482 [info] Access ElixirTestOneWeb.Endpoint at http://8080:8080

$ » curl http://localhost:8080/api/keys | jq
% Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                  Dload  Upload   Total   Spent    Left  Speed
100    29  100    29    0     0   4583      0 --:--:-- --:--:-- --:--:--  4833
{
    "keys": {
        "payment_key": "pk"
    }
}

At this point, you can orchestrate your deployment making proper configuration provisioning.

The future

Version 1.9 of Mix.Config will contain the release command, so to import the release features now offered by distillery and much more. Read about it here.

Note: the code for this article is published here.

Photo by John Barkiple on Unsplash

Did you like this article?

Bruno Ripa

CEO & Founder @ Polaris BR

See other articles by Bruno

Related jobs

See all

Title

The company

  • Remote

Title

The company

  • Remote

Title

The company

  • Remote

Title

The company

  • Remote

Related articles

JavaScript Functional Style Made Simple

JavaScript Functional Style Made Simple

Daniel Boros

12 Sep 2021

JavaScript Functional Style Made Simple

JavaScript Functional Style Made Simple

Daniel Boros

12 Sep 2021

WorksHub

CareersCompaniesSitemapFunctional WorksBlockchain WorksJavaScript WorksAI WorksGolang WorksJava WorksPython WorksRemote Works
email iconhello@works-hub.comUK flag

Ground Floor, Verse Building, 18 Brunswick Place, London, N1 6DZ

US flag

108 E 16th Street, New York, NY 10003

Subscribe to our newsletter

Join over 111,000 others and get access to exclusive content, job opportunities and more!

© 2022 WorksHub

Privacy PolicyDeveloped by WorksHub