Elixir release with Distillery

Elixir release with DistilleryBruno RipaBlockedUnblockFollowFollowingMay 24After 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 timeDuring development you run your application by launching$ mix phx.

serverand probably you have a set of exported environment variables that are used by your application, in config.

exs, 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 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: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.

serverCompiling 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 Speed100 29 100 29 0 0 475 0 — : — : — — : — : — — : — : — 483{ "keys": { "payment_key": "pk" }}And it will work for all the other environments (try running MIX_ENV=prod PAYMENT_KEY=pk mix phx.

server and verify).

Release creationLet’s try what happens if we create a release build:# Needed to generate the release configuration file$ MIX_ENV=prod mix release.

initAn 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 startIf 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 attachFor a complete listing of commands and their use:> _build/prod/rel/app/bin/app helpand run it as suggested by the resulting output:$ PAYMENT_KEY=pk HOSTNAME=localhost PORT=8080 _build/prod/rel/app/bin/app foregroundTrying 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 Speed100 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 providersThe 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 (more info here).

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/runtime.

exs to ${RELEASE_ROOT_DIR}/config/runtime.

exsand use it as configuration file.

So, if we now build the app, everything will work fine:# Needed to generate the release configuration file$ MIX_ENV=prod mix release.

initAn 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 startIf 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 attachFor a complete listing of commands and their use:> _build/prod/rel/app/bin/app help$ PAYMENT_KEY=pk HOSTNAME=localhost PORT=8080 _build/prod/rel/app/bin/app foreground13: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 CurrentDload Upload Total Spent Left Speed100 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 futureVersion 1.

9 of Mix.

Configwill contain the release command, so to import the release features now offered by distillery and much more.

Read about it here: https://hexdocs.

pm/mix/master/Mix.

Tasks.

Release.

html.

Note: the code for this article is published here: https://github.

com/brunoripa/elixir-release-distilleryPhoto by John Barkiple on Unsplash.

. More details

Leave a Reply