fireproofsocks

fireproofsocks

Config.exs and System.get_env/2: are values actually read at runtime?

I thought I had a handle on how Elixir configuration worked, but now I’m thinking I missed something. My memory was that the config.exs file gets read at compile-time. So if my config.exs contained the following:

# config.exs
config :myapp, :foo, System.get_env("FOO")

Then I could compile an option into it via

FOO=bar mix compile

and starting up the app would yield the value that I set at compile time, e.g.

iex -S mix

# Expected:
iex> Application.get_env(:myapp, :foo)
"bar"

But the actual result is nil. Or whatever the System ENV is set to at runtime – and the app does not re-compile.

So my question is: is config.exs actually reading values at runtime? Or is it doing something special with System.get_env/2 so it resolves at runtime? Am I going crazy? I don’t remember it working this way. Thanks for any clarifications!

Most Liked

v0idpwn

v0idpwn

The environment is set by mix when starting up, both in compile time and when running your application. If you’re using releases, then the config is set at the point where the release is built (except for runtime.exs, which is evaluated at startup in releases as well).

al2o3cr

al2o3cr

config.exs is still an exs file, so it’s evaluated at runtime.

My guess is that you’re remembering two similar things:

  • using System.get_env in some places in modules will result in the “stay until the next compile” behavior you describe:

      defmodule SomeModule do
        @foo System.get_env("FOO")
    
        def foo, do: @foo
     end
    

    SomeModule.foo will “capture” the value from ENV at compile-time

  • using System.get_env in config.exs will result in a “stay until the next release” behavior for environment variables, thus the need for runtime.exs

lud

lud

@v0idpwn I am surprised actually, I too was believing that this was compile-time only but indeed the values defined here must be available at runtime and are not stored into a file like in a release.

So the runtime execution erases the value set before compilation.

What is confusing is that if you wanted to have this config:

import Config
config :demo, foo: System.get_env("FOO")

And this code:

defmodule Demo do
  @foo Application.get_env(:demo, :foo)

  def foo do
    @foo
  end
end

You cannot call FOO=bar mix compile and then just iex -S mix and get the expected value, because iex -S mix will always trigger a recompilation. The code above will emit a warning as you should use Application.compile_env. Very different behaviour than calling @foo System.get_env("FOO") directly.

Personnally, I just put everything possible in runtime.exs.

v0idpwn

v0idpwn

no system env vars are implicitly baked into the release

Yes, they are.

Quoting the documentation, here: mix release — Mix v1.12.3

The :secret_key key under :my_app will be computed on the
host machine, whenever the release is built. Therefore if the machine
assembling the release not have access to all environment variables used
to run your code, loading the configuration will fail as the environment
variable is missing. Luckily, Mix also provides runtime configuration,
which should be preferred and we will see next.

Small demonstration:

# config/config.exs
import Config 

config :config_demonstration, :my_config, , IO.puts """
:::::::::::::::::::::::
:::::::::::::::::::::::
:::: CFG EVALUATED ::::
:::::::::::::::::::::::
:::::::::::::::::::::::
"""

Now, if I run:

v0idpwn ~/oss/config_demonstration [master] $ mix compile
:::::::::::::::::::::::
:::::::::::::::::::::::
:::: CFG EVALUATED ::::
:::::::::::::::::::::::
:::::::::::::::::::::::

Compiling 1 file (.ex)
Generated config_demonstration app
v0idpwn ~/oss/config_demonstration [master] $ iex -S mix
Erlang/OTP 25 [erts-13.1.4] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit]

:::::::::::::::::::::::
:::::::::::::::::::::::
:::: CFG EVALUATED ::::
:::::::::::::::::::::::
:::::::::::::::::::::::

Interactive Elixir (1.14.3) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)>

You can see it was evaluated both at compile time and when starting the application with mix.

Now, I will generate my release:

v0idpwn ~/oss/config_demonstration [master] $ mix release
:::::::::::::::::::::::
:::::::::::::::::::::::
:::: CFG EVALUATED ::::
:::::::::::::::::::::::
:::::::::::::::::::::::

* assembling config_demonstration-0.1.0 on MIX_ENV=dev
* skipping runtime configuration (config/runtime.exs not found)
:::::::::::::::::::::::
:::::::::::::::::::::::
:::: CFG EVALUATED ::::
:::::::::::::::::::::::
:::::::::::::::::::::::


Release created at _build/dev/rel/config_demonstration

You can see it was evaluated two times.

And run it:

v0idpwn ~/oss/config_demonstration [master] $ ./_build/dev/rel/config_demonstration/bin/config_demonstration start_iex
Erlang/OTP 25 [erts-13.1.4] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit]

Interactive Elixir (1.14.3) - press Ctrl+C to exit (type h() ENTER for help)
iex(config_demonstration@Host)1> Application.get_env(:config_demonstration, :my_config)
:ok

You can see it was not evaluated, yet the result value is there.

fireproofsocks

fireproofsocks

I didn’t mean to cause any confusion. “Snapshot” seems like a good term for how values are read during release builds. Thanks for the link to say_cheez_ex.

I just put everything possible in runtime.exs .

Me too. I wrote the dotenvy package to help make it easier to work with configuration sets at runtime.

The takeaways for me are to evaluate whether or not something NEEDS to be known at compile time. Often, a value can be supplied at runtime just fine. Supplying values at runtime can make testing easier, but there may be performance tradeoffs, so YYMV.

I wrote an article about this Dotenvy and my first dig into config:
https://fireproofsocks.medium.com/configuration-in-elixir-with-dotenvy-8b20f227fc0e

Where Next?

Popular in Questions Top

gshaw
What is the idiomatic way of matching for not nil in Elixir? E.g., First way: defp halt_if_not_signed_in(conn, signed_in_account) when...
New
lk-geimfari
What is most correct way to open, read and parse JSON file with poison? For example if we have example.json file in root of some projec...
New
lastday4you
I wanted to check elixir version in phoenix because i found that my elixir is 1.5 but when i use Enum.chunk_by it said the function is un...
New
makeitrein
Hey all, just started picking up Elixir last week and am writing a scraper as a learning project. Baby step #1 is extracting the number ...
New
qwerescape
Is there a way to get the call stack or stack trace at any point in the code? Not from exceptions, but an expression that returns how the...
New
baxterw3b
Hi guys, i’m new in the Elixir world, and i have to say, that i love it! i’m having some problem to understand anonymous functions with ...
New
hpopp
To simplify some tasks at work, I wrote and published this package yesterday. It’s a simple macro that enables Access behaviour on struct...
New
wernerlaude
In AR this is so simple @articles = current_user.articles How to do in Ecto? def index(conn, _params) do current_user = conn.assig...
New
skosch
To my knowledge, put_in, Map.update etc. all have the one limitation of not automatically creating intermediate keys when needed (for exa...
New
jay1
Why is it that the mnesia database isn’t the most preferred database for use in Elixir/Phoenix?
New

Other popular topics Top

JakeBecker
TL;DR: I’ve just released an implementation of Microsoft’s IDE-independent Language Server Protocol for Elixir. It adds language support ...
1140 51847 244
New
sergio
In Ruby, I can go: User.find_by(email: "foobar@email.com").update(email: "hello@email.com") How can I do something similar in Elixir? ...
New
axelson
This post is a wiki (feel free to hit the edit button near the bottom right of this post to add your own changes!) This post collects co...
239 45766 226
New
stefanchrobot
What’s the safe way to decode a JSON string into a struct? I want to avoid calling String.to_atom. Jason.decode can give me a map with st...
New
vac
Hi, I'm quite new in Elixir and I'm trying to format a string to a PEM format. I have the certificate value like MIIDBTCCAe2...... and ...
New
stefanluptak
Hello everybody, usually, I use a 29" ultra-wide monitor for VSCode which can easily accomodate explorer (files panel) + file with code ...
New
chrisalley
ExUnit now has describe blocks which is a welcome addition coming from RSpec. In the docs, it states that nested hierarchies of describe ...
New
Fl4m3Ph03n1x
About me? ( if you have nothing better to do than reading about some random guy in the internet :stuck_out_tongue: ) Hello all, this is ...
New
AstonJ
We’ve put together this wiki for Phoenix LiveView - please feel free to add any info you feel is worth including. What is Phoenix LiveV...
New
magnetic
Hey :wave:t3: Elixir community, I’ve been learning Elixir, and working on some side projects. My editor of choice is VSCode, and althoug...
New

We're in Beta

About us Mission Statement