Why can't I do configuration in runtime.exs in my dev environment?

So I’m trying to understand how config/runtime.exs works. If I put:

IO.inspect("in runtime.exs")

at the top of the file, then when I start my application on my local machine with mix phx.server I can see it print out “in runtime.exs” in my terminal, and I can add other print statements to confirm that runtime.exs is being executed after dev.exs, which is being executed after config.exs.

But if I set some configuration in runtime.exs then it does not override the configuration set in config.exs, and of course it also does not override the configuation set in dev.exs. However, if I put some configuration in dev.exs then it does override the configuration set in config.exs.

My understanding is that runtime.exs is the last file executed of these three, and the first file executed when the compiled application starts up, so I would think that configuration set in runtime.exs would override configuration set in dev.exs. But this is not happening.

Does anyone have any idea what is going on here?

I’m using Phoenix 1.6-rc.0, by the way.

Also, I notice that config/runtime.exs is apparently executed after lib/myapp/application.ex is executed when I start my app locally with mix phx.server. This is not what I was expecting.

I think of lib/myapp/application.ex as the entry point to the actually running app, which should happen after everything is configured. So runtime.exs should be executed after compilation, but before the app actually starts, because I will need to load secret API keys through runtime.exs, which my app will need when it starts. But if I’m already in lib/myapp/application.ex before runtime.exs has executed then isn’t that the wrong order?

What am I not understanding? And how can I set configuration through runtime.exs and have it actually override configuration set in the config files?

Thanks.

What kind of settings are you seeing not being overriden via runtime.exs? Are you by any chance trying to change a compile-time config param via runtime.exs?

in config.exs I have the following lines:

config :myapp,
  secret_api_key: "set-in-config.exs"
IO.inspect('in config.exs...')

in dev.exs I have the following lines:

config :myapp,
  secret_api_key: "set-in-dev.exs"
IO.inspect('in dev.exs...')

in runtime.exs I have the following lines:

config :myapp,
  secret_api_key: "set-in-runtime.exs"
IO.inspect('in runtime.exs...')

When I start the app, I see it print
‘in config.exs…’
‘in dev.exs…’
‘in runtime.exs…’

but the value of secret_api_key is “set-in-dev.exs”, at least as far as I can tell.

Also, if I add IO.inspect to lib/myapp/application.ex then it sometimes prints when I start the app up and it sometimes does not – I would like to be able to reliably IO.inspect my environment in application.ex and I don’t understand why that doesn’t work reliably.

I have the following lines at the bottom of lib/myapp/application.ex:

IO.inspect("in lib/myapp/application.ex")
IO.inspect(Application.get_all_env(:myapp))

When I start the app for the first time after adding those lines it will print “in lib/myapp/application.ex” and then print the environment so I can inspect it – and it will do this BEFORE it prints “in runtime.exs”. But if I then stop and restart the app it will not print the lines from lib/myapp/application.ex.

So yes, I think I am trying to change a compile-time config param via runtime.exs. Is this not possible? If not, then why not?

How are you reading the config value? If you’re reading it during compile-time, then it’s baked into the app and trying to modify at runtime will have no effect.

At the time runtime.exs is read compilation is long done. So you cannot change compile time values through runtime.exs. This is even more apparent when you build a release. Compilation happens when the release is created, but runtime.exs is only evaluated once the release is moved to wherever it’s supposed to run and started there.

If you need things to be available at compile time use config.exs and any additional files imported from there.

I understand that runtime.exs is executed long after the compilation is finished. This is why I am so interested in setting my secret API key through runtime.exs rather than through config.exs or one of dev/test/prod.exs.

I would like for runtime.exs to be evaluated only once the release is moved to wherever it is supposed to run and started there, and it is my understanding that that is what happens.

On my local development environment I see that runtime.exs is in fact evaluated after config.exs and dev.exs are evaluated. Surely it is possible to use runtime.exs to set a secret API key, since that is the whole purpose of the file, as I understand it.

Is it not possible to overwrite values in runtime.exs that have previously been set in config.exs?

How are you reading the config value? If you’re reading it during compile-time, then it’s baked into the app and trying to modify at runtime will have no effect.

I wonder if I don’t understand how to inspect my final runtime configuration properly. How can I inspect a value in my environment while the application is running, long after runtime.exs has finished doing it’s thing?

The issue is here. These lines that are at the bottom of the file will be executed at compile time. Thus runtime.exs will not be evaluated yet. Any values set in runtime.exs are only available at runtime.

I wrote this blog post that attempts to clarify the differences between the configurations (in the article I call “compile time” “build time” but it means the same thing): Elixir: Time for Some Configuration – Random Notes

To simplify, if you want to see your values from runtime.exs, you need to grab them at runtime, i.e. inside a function that is called at runtime.

2 Likes

To clarify this further. In elixir any code, which is not within a function definition (or moved to such via a macro) will be executed at compile time.

3 Likes

So basically try this:

defmodule MyApp.Application do
  use Application

  def start(_type, _args) do
    IO.inspect(Application.get_all_env(:myapp))
    # ...
  end
end
1 Like

Ok, so I had assumed that runtime.exs is evaluated at runtime, but I had the following lines in runtime.exs:

config :myapp,
  jon: "jon-runtime"

IO.inspect('in runtime.exs...')
IO.inspect(Application.fetch_env(:myapp, :jon))

and when I started the app via mix phx.server I was seeing the following output in my terminal:

'in runtime.exs...'
{:ok, "jon-dev"}

Since I had set the variable “jon” to “jon-dev” in my dev.exs file, it appeared to me that runtime.exs was not overwriting this value at runtime, which is when I thought runtime.exs was running.

Can anyone explain why I see the output

'in runtime.exs...'
{:ok, "jon-dev"}

if runtime.exs is executed only at runtime and not at compile time?

Or is runtime.exs evaluated but not executed, or something like that, at compile time? What is going on there when I set the value to “jon-runtime” in runtime.exs and then immediately inspect it and have it print out “jon-dev” instead?

This is an implementation detail: the config ... line actually does not set the application env at that point, it puts values to the process dictionary with Process.put. The application env is created later when the system starts proper. Thus if you want to share some common values inside runtime.exs (or other config files), you must put them in regular variables.

Try inspecting the values inside your application.ex start-callback. That function is executed at runtime and you should see the correct values there (and any other function executed at runtime).

4 Likes

I think the easiest way that you can check your config here, would be to start the app like iex -S mix and call Application.fetch_env(:myapp, :jon) inside iex

2 Likes

Ok, perfect. This is what had me confused. I have a much better understanding of what is happening now. It seems that runtime.exs is evaluated after compilation, but before “the system starts proper” – so it’s in that liminal, hard-to-understand space.

Tangent question: with what elixir release was runtime.exs introduced?

Elixir 1.9 I believe.

The releases.exs file was released in elixir 1.9, this was the first attempt at runtime config but it only applied to releases and not in dev. Elixir 1.11 introduced runtime.exs which runs in both dev and release mode.

3 Likes