Does a library’s configuration need to be declared as runtime to work correctly in a production release?
0 reputation · 29 Aug 2023, 06:55 UTC
0 reputation · 29 Aug 2023, 06:55 UTC
When developing an Elixir application with Mix, configuration files in config/ are loaded on each start, so changes to config/prod.exs take effect immediately. In a production release built with mix release or Distillery, static configuration is compiled into sys.config; only values wrapped in {:system, "VAR"} or placed in config/runtime.exs are read from the environment at startup.
The unresolved decision is determining which configuration keys should be declared as runtime versus compile‑time, especially when third‑party libraries may expect a value during compilation. Moving a key to config/runtime.exs can prevent the release from baking in a development value, but it may break libraries that read the key at compile time or rely on it being present in sys.config.
Does marking a key as runtime guarantee it will be read at startup even if a dependency expects it at compile time? Which configuration keys are safe to move to runtime without breaking library behavior? When should a library author provide both compile‑time and runtime paths for the same setting?
35275 reputation · 29 Aug 2023, 10:59 UTC
No — a library's configuration does not need to be declared as runtime to work correctly in a production release. Compile-time configuration baked into sys.config works fine; it just can't be changed without rebuilding the release. And moving a key to config/runtime.exs does not guarantee it will be read at startup in every case: if a dependency reads that key during its own compilation (macro expansion, module attributes, generated code), it captured the value before your runtime config ever loaded. The decision is per-key, based on when the value is consumed.
When you run mix release, everything in config/config.exs, config/prod.exs, etc. is evaluated at build time and written into the release's sys.config. config/runtime.exs (Elixir 1.11+) is copied into the release and evaluated on the target machine at boot, before applications start. So:
System.get_env/1) on each boot. Ideal for secrets, hostnames, ports.The failure mode is ordering: a dependency's code is compiled before your runtime config exists. If a library does something like @adapter Application.compile_env(:my_app, :adapter) or branches on config inside a macro, it bakes in whatever was present at compile time — typically the default or nil. Moving that key to runtime.exs won't crash the build; it will silently use the stale value, which is worse.
Modern libraries signal their intent through the API they call:
Application.compile_env/3 — the value is needed at compile time. Keep it in config.exs/prod.exs. Moving it to runtime triggers a warning or stale behavior.Application.fetch_env!/2 or Application.get_env/2 called inside start/2 or at runtime — safe to move to runtime.exs.Generally safe, because they're consumed at application start, not compile time:
init/2 or at runtime)Endpoint runtime config is the canonical example)Generally unsafe to move:
Application.compile_env (adapters, behaviours, compile-time dispatch)A library should offer a dual path when the setting has a compile-time component (e.g., choosing a module) but also a runtime component (e.g., the module's connection details). The common pattern: read the structural choice with compile_env, and read credentials/URLs in an init/1 or start_link/1 callback so users can supply them from runtime.exs. Phoenix and Ecto both follow this split.
Build a release and inspect what got baked in:
MIX_ENV=prod mix release
# The moved key should NOT appear here:
grep -r "my_key" _build/prod/rel/my_app/releases/*/sys.configThen boot with the environment variable set and confirm the value is picked up — for example, log Application.get_env(:my_app, :my_key) in your application's start/2. If the app misbehaves after moving a key, search the dependency's source for compile_env or module attributes referencing that key; if found, move it back to compile-time config with a safe default.
One caveat: changing runtime.exs requires rebuilding the release artifact (the file is copied in), though the values it reads from the environment can change per boot without a rebuild.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.