Why Phoenix isn't merging `config.exs` and `runtime.exs` configs?

Ok, I’ve an app that I intend to use releases on future, so I decided to create a runtime.exs, that means: generic configuration file that is evaluated on runtime on every env, correct?

Also, as a Phoenix app, we have config.exs, a config file that is evaluated at compile time, correct?

Doubt number one: which one come first: runtime or compile time?

My problem:
This is my config.exs:

import Config

config :lovelace,
  ecto_repos: [Lovelace.Repo],
  generators: [binary_id: true]

# Configures the endpoint
config :lovelace, LovelaceWeb.Endpoint,
  url: [host: "localhost"],
  secret_key_base: "+41x5h8Xbn3ilu0YZ5UShcHI2/qhY3JGZpT8ockkzdkTHahMJe177aE2dcyQ5CAn",
  render_errors: [view: LovelaceWeb.ErrorView, accepts: ~w(html json), layout: false],
  pubsub_server: Lovelace.PubSub,
  live_view: [signing_salt: "XP1985o+"]

# Configures Elixir's Logger
config :logger, :console,
  format: "$time $metadata[$level] $message\n",
  metadata: [:request_id]

# Use Jason for JSON parsing in Phoenix
config :phoenix, :json_library, Jason

config :tesla, adapter: Tesla.Adapter.Hackney

config :lovelace, pubsub_channel: Lovelace.PubSub

# Import environment specific config. This must remain at the bottom
# of this file so it overrides the configuration defined above.
import_config "#{config_env()}.exs"

And this is my runtime.exs:

import Config

get_env_var = fn var_name, default ->
  value = System.get_env(var_name)

  cond do
    default != :none ->
      default

    is_nil(value) or value == "" ->
      raise """
      Environment variable #{var_name} is missing!
      """

    true ->
      value
  end
end

# server config
app_host = get_env_var.("HOST", "localhost")
app_port = get_env_var.("PORT", "4000") |> String.to_integer()

# general config
config :lovelace, bot_name: "lovelace"

if config_env() == :prod do
  # bot config
  bot_token = get_env_var.("BOT_TOKEN", :none)

  # database config
  db_url = get_env_var.("DB_URL", :none)
  pool_size = get_env_var.("POOL_SIZE", "2") |> String.to_integer()

  # server config
  secret_key_base = get_env_var.("SECRET_KEY_BASE", :none)

  config :lovelace, bot_token: bot_token

  config :lovelace, Lovelace.Repo,
    url: db_url,
    pool_size: pool_size

  config :lovelace, Lovelace.Endpoint,
    server: true,
    url: [host: app_host, port: app_port],
    http: [
      port: app_port,
      transport_options: [socket_opts: [:inet6]]
    ],
    secret_key_base: secret_key_base
end

When I run:
MIX_ENV=prod DB_URL=xxxxx SECRET_KEY_BASE=xxxxx BOT_TOKEN=xxxx PORT=4000 mix phx.server

where “xxxx…” == some real config,

I receive this output:

21:54:54.910 [info] Starting Elixir.LovelaceIntegration.Telegram.Consumers.MessageHandler
21:54:55.071 [info] Access LovelaceWeb.Endpoint at http://localhost

So, the server don’t really starts…

When I execute the same command, but using iex -S mix, and then running Application.get_all_env :lovelace, I receive this result:

iex(1)> Application.get_all_env :lovelace
[
  {Lovelace.Repo,
   [
     url: "xxxxxx",
     pool_size: 2
   ]},
  {:pubsub_channel, Lovelace.PubSub},
  {:ecto_repos, [Lovelace.Repo]},
  {:bot_token, "xxxxx"},
  {:bot_name, "lovelace"},
  {Lovelace.Endpoint,
   [
     server: true,
     url: [host: "localhost", port: 4000],
     http: [port: 4000, transport_options: [socket_opts: [:inet6]]],
     secret_key_base: "xxxxxx"
   ]},
  {:generators, [binary_id: true]},
  {LovelaceWeb.Endpoint,
   [
     url: [host: "localhost"],
     secret_key_base: "xxxxx",
     render_errors: [
       view: LovelaceWeb.ErrorView,
       accepts: ["html", "json"],
       layout: false
     ],
     pubsub_server: Lovelace.PubSub,
     live_view: [signing_salt: "XP1985o+"]
   ]}
]

Again, “xxxx” == sensity info

So, it seems that the config.exs and runtime.exs configs, actually, only the LovelaceWeb.Endpoint config, isn’t being merged, so the last one (config.exs) is being evaluated.

If I run mix phx.server, on dev env, server starts correctly:

Generated lovelace app
[info] Starting Elixir.LovelaceIntegration.Telegram.Consumers.MessageHandler
[info] Running LovelaceWeb.Endpoint with cowboy 2.8.0 at 0.0.0.0:4000 (http)
[info] Access LovelaceWeb.Endpoint at http://localhost:4000

webpack is watching the files…

[hardsource:321e22a3] Using 1 MB of disk space.
[hardsource:321e22a3] Tracking node dependencies with: yarn.lock.
[hardsource:321e22a3] Reading from cache 321e22a3...
Hash: 59927b50d778eaad2717
Version: webpack 4.41.5
Time: 212ms
Built at: 02/16/2021 10:02:48 PM
                Asset       Size  Chunks             Chunk Names
       ../css/app.css   12.9 KiB     app  [emitted]  app
       ../favicon.ico   1.23 KiB          [emitted]
../images/phoenix.png   13.6 KiB          [emitted]
        ../robots.txt  202 bytes          [emitted]
               app.js    278 KiB     app  [emitted]  app
Entrypoint app = ../css/app.css app.js
[0] multi ./js/app.js 28 bytes {app} [built]
    + 9 hidden modules

Also tests works fine!

What I’m doing wrong?

You need to be on Elixir 1.11.* to be able to use runtime.exs, what version are you in?

1 Like

Sorry, I didn’t provide this information!

elixir -v:

Erlang/OTP 23 [erts-11.1.8] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1] [hipe]

Elixir 1.11.3 (compiled with Erlang/OTP 23)

erlang OTP v23.2.5

phx.new v1.5.7

I also took a look into endpoint.ex and endpoint/supervisor.ex file on phoenix repo, on:

And these information really clarified me, but didn’t solve the problem.

Try to replace the call to this function with System.fetch_env!("ENV_VAR_NAME") or with System.get_env("ENV_VAR_NAME", "default_value").

Same problem :confused:

Output:

21:54:54.910 [info] Starting Elixir.LovelaceIntegration.Telegram.Consumers.MessageHandler
21:54:55.071 [info] Access LovelaceWeb.Endpoint at http://localhost

Application.get_all_env/1:

iex(1)> Application.get_all_env :lovelace
[
  {Lovelace.Endpoint,
   [
     server: true,
     url: [host: "localhost", port: 4000],
     http: [port: 4000, transport_options: [socket_opts: [:inet6]]],
     secret_key_base: "xxxx"
   ]},
  {Lovelace.Repo,
   [
     url: "xxxxx",
     pool_size: 2
   ]},
  {:pubsub_channel, Lovelace.PubSub},
  {:ecto_repos, [Lovelace.Repo]},
  {:bot_token, "xxxxx"},
  {:bot_name, "lovelace"},
  {:generators, [binary_id: true]},
  {LovelaceWeb.Endpoint,
   [
     url: [host: "localhost"],
     secret_key_base: "xxxxx",
     render_errors: [
       view: LovelaceWeb.ErrorView,
       accepts: ["html", "json"],
       layout: false
     ],
     pubsub_server: Lovelace.PubSub,
     live_view: [signing_salt: "xxxx"]
   ]}
]

Well, Know I’m embarrassed with such a dumb error…

On config.exs I correctly used config :lovelace, LovelaceWeb.Endpoint, however, on runtime.exs I wrongly set up as config :lovelace, Lovelace.Endpoint

Well sorry for this… I’m working on this project all day long, maybe it’s time to take a break haha

But all the research about compile time and runtime configs were so enlightening!

5 Likes

This command is missing the HOST env var.

nope! the HOST env var is optional, default to localhost

But once you are using MIX_ENV=prod I though you were trying to run it in production.