Best way to handle env vars that must be present at runtime but not compile time?

I’m trying to figure out the best way to handle an env var that MUST be present at runtime but not at compile time. The following is used to generate this env var:

  defp vault_key(_) do
    vault_key = :base64.encode(:crypto.strong_rand_bytes(16))
    IO.puts("#{vault_key}")
  end

Effectively I have an env var called VAULT_KEY which must be present at runtime to encrypt and decrypt values in mnesia, but I don’t want to require it at compile time, as if it’s unset, you can’t fire off this mix task to generate and export the key in the first place. Using System.get_env("VAULT_KEY") allows it to compile, but of course sets it to nil if the env var wasn’t set prior to compiling, which is not the desired behavior. Using System.fetch_env!("VAULT_KEY") requires the key to be present, but won’t allow for compilation if not present. The behavior I’m trying to accomplish here is that if the key isn’t present at compile time, thats’s fine, but if it’s not present at runtime then I the application should bail and fail to start. And I wan’t to apply that to all env’s (dev, test, staging, prod). Any thought’s or pointers would be greatly appreciated.

If the call to System.fetch_env! is inside a function (and that function is not called during compilation) then it won’t be evaluated at compile time, so your code will compile even if it is not set.

If the call to System.fetch_env! is not inside a function (for example if it is inside a module and being assigned to a module attribute) then it will be evaluated at compile time and will result in an error.

4 Likes

Ok, tell me if this tracks then: handle this by seeing if I can fetch the env variable in a func in application.ex (or called in application.ex) and System.stop/1 if unable to? Something like this:

  defp set_vault do
    if Application.get_env(:vanguard, :vault_key) == nil do
      case System.fetch_env("VAULT_KEY") do
        {:ok, vault_key} ->
          Application.put_env(:vanguard, :vault_key, vault_key)

        :error ->
          Logger.error("VAULT_KEY is unset! Ensure VAULT_KEY has been generated and exported!")
          System.stop(1)
      end
    end
  end

Then put this at the top of the start func in application.ex

:wave:

Just in case, there is also a way to do it via distillery’s (and probably elixir 1.9’s) config providers. I usually have something like

# in the config provider
use Mix.Config

# evaluated on init
config :vanguard, vault_key: System.get_env("VAULT_KEY") || raise("VAULT_KEY is not not set.")

Example

2 Likes

In 1.9 I think the convention is just to use System.fetch_env!/1 in config/releases.exs. Though you can use config providers with elixir releases too according to the docs.

Sometimes there is a need to explain to the user why and how an env var should be set, not just that it’s missing.

1 Like

That’s totally fair and a valid point to bring up.