It’s very common (and highly recommended) that application keeps its configuration values separated from its version control. A way of doing this is by using ENV vars (environment variables). They’re being used for improvements mostly on maintainability. The 12-factor app manifesto explains it on its Configuration section:

The twelve-factor app stores config in environment variables (often shortened to env vars or env). Env vars are easy to change between deploys without changing any code; unlike config files, there is little chance of them being checked into the code repo accidentally; and unlike custom config files, or other config mechanisms such as Java System Properties, they are a language- and OS-agnostic standard.

In an Elixir project, the config goes in Mix.Config files. Some examples are: config.exs and environment config files ( dev.exs , test.exs and prod.exs ). These files are generally used by frameworks and libraries, but they have already proven useful for using mocks in our tests.

Let’s take an Ecto config as example:

# config/dev.exs config :myapp, MyApp.Repo, adapter: Ecto.Adapters.Postgres, username: "postgres", password: "postgres", database: "myapp_dev", hostname: "localhost", pool_size: 10

A well-known approach is using Environment variables to hide and scope these values through different environments. To use it, we just need to have a configured variable and get it in our application. In Elixir we do this easily with System.get_env("ENV_VAR") .

We could configure our last example with this approach:

# config/dev.exs config :myapp, MyApp.Repo, adapter: Ecto.Adapters.Postgres, username: System.get_env("DB_USER"), password: System.get_env("DB_PASSWORD"), database: System.get_env("DB_NAME"), hostname: System.get_env("DB_HOST"), pool_size: 10

This way you won’t expose your database configs and will actually make things more dynamic. In development this is useful because the developers won’t need to make changes on this file, they’ll just need to export these vars.

So far this isn’t much different from what we do in other languages. However, things start to happen differently when we try to generate an Exrm release to deploy our app in production.

ENV vars need to be present during compile time

We all already know that Elixir is a compiled language. And in order to deploy or generate a release we need to compile our application. So everything is compiled, even our config files! Then, there’s an interesting behavior while compiling our config files.

Our System.get_env() calls will be evaluated during the compilation, so the binaries will be generated with the current value of the ENV var. Because of this, we need all of our environment variables to be exported during compiling. When we don’t have them, their value will be nil and we won’t be able to connect to our database, for example. This way, to build a release we’d need all our environment variables where we’re building it (our own machine or a build server).

If we’re working with Phoenix, there is an exception. Phoenix has a special way of configuring an HTTP port with ENV vars that evaluates it during runtime.

config :myapp, MyApp.Endpoint, http: [port: {:system, "PORT"}], # ...

It works great and data won’t be fixed in the release, but it’s specific for this Phoenix config. But don’t be sad! There are already some mature discussions around this in the Exrm repo, take a look, you may be able to help!

There’s a way when using Exrm release

I was chatting around Elixir Slack channel when our friend Ranelli mentioned that there was a simple technique that we could use to solve this when we build an Exrm release. Instead of using System.get_env in our configs, we must use "${ENV_VAR}" . Then, we just need to run our release with RELX_REPLACE_OS_VARS=true .

RELX_REPLACE_OS_VARS=true rel/myapp/bin/myapp start

This will make our release to use the values represented by these special strings. I’ll explain.

An Exrm release has two important files: sys.config and vm.args . These files are responsible by the data used in production (usually what’s in config.exs and prod.exs ) and specific configs that we can make of the Erlang VM respectively.

sys.config

[{sasl,[{errlog_type,error}]}, {logger, [{console, [{format,<<"$time $metadata[$level] $message

">>}, {metadata,[request_id]}]}, {level,info}]}, {myapp, [{'Elixir.MyApp.Endpoint', [{root,<<"/Users/igorffs/src/myapp">>}, {render_errors,[{accepts,[<<"html">>,<<"json">>]}]}, {pubsub, [{name,'Elixir.MyApp.PubSub'}, {adapter,'Elixir.Phoenix.PubSub.PG2'}]}, {http,[{port,<<"${PORT}">>}]}, {url,[{host,<<"localhost">>}]}, {cache_static_manifest,<<"priv/static/manifest.json">>}, {server,true}, {secret_key_base, <<"${SECRET_KEYBASE}">>}]}, {'Elixir.MyApp.Repo', [{adapter,'Elixir.Ecto.Adapters.Postgres'}, {username,<<"${DB_USER}">>}, {password,<<"${DB_PASSWORD}">>}, {database,<<"${DB_NAME}">>}, {hostname,<<"localhost">>}, {pool_size,10}, {port,<<"15432">>}]}]}, {phoenix,[{generators,[{migration,true},{binary_id,false}]}]}].

vm.args

## Name of the node -sname myapp ## Cookie for distributed erlang -setcookie myapp ## Heartbeat management; auto-restarts VM if it dies or becomes unresponsive ## (Disabled by default..use with caution!) ##-heart ## Enable kernel poll and a few async threads ##+K true ##+A 5 ## Increase number of concurrent ports/sockets ##-env ERL_MAX_PORTS 4096 ## Tweak GC to run more often ##-env ERL_FULLSWEEP_AFTER 10

Exrm is using a lib called relx under the hood to build its releases. When we exported RELX_REPLACE_OS_VARS=true relx will make a replace of the strings by their correspondent ENV var values in the config files.

{'Elixir.MyApp.Repo', [{adapter,'Elixir.Ecto.Adapters.Postgres'}, {username,<<"${DB_USER}">>}, {password,<<"${DB_PASSWORD}">>}, {database,<<"${DB_NAME}">>}, {hostname,<<"localhost">>}, {pool_size,10}, {port,<<"15432">>}]}

You’ve noticed where our special strings are in the sys.config , and if you guessed that this process can be done manually, you got it! But this replace really makes things easier for us. Otherwise, we would have to edit every option in the file. It’s very important to mention, if you change those files, you’ll have to reboot your application.

Considerations

This subject is very important if we’re going on production. It concerned us a bit when we’ve noticed that we couldn’t have more dynamic configs. This replacement solution was a relief. Make sure to keep following the discussion I mentioned before, things are probably going to change after it.

Have you already been in trouble dealing with ENV vars? How did you solve it?





