Phoenix environment variables not available at runtime

when I use
@base_url System.get_env("MS_URL")
on localhost, it correctly identifies the variable and sets the endpoint properly.

but after I set my secrets via fly secrets set MS_URL=$MS_URL" my url is only an empty string.

Any idea why?

Strange. I guess the first thing to check would be to see what value is being sent to Fly. So make sure $MS_URL is the value you expect (e.g echo "$MS_URL") and not an empty string.

I’ve found using variables is complicated by running commands like fly secrets set in a script (like bash) vs directly (like in Terminal, on a Mac). Sometimes the variable you think should be there/accessible (and so being set) isn’t. Debugging with an echo can confirm it is. And then proceed accordingly.

1 Like

Does echo $MS_URL output the expected output?
Perhaps there’s a char that isn’t valid bash, you could try quoting the variable to prevent special char expansion:
fly secrets set MS_URL="$MS_URL"

1 Like

Which file are you using System.get_env("MS_URL") in?

The thing that always gets me on Phoenix is compile time environment variables. config/config.ex, config/prod.exs are executed at compile time, if you use env variables there you won’t see your app secrets. config/runtime.exs will see secrets because it’s executed at runtime.

I am using System.get_env("MS_URL") it just before I make http call via Tesla to define my base url.

It is in lib/myapp/api.ex which handles all the external api calls I need to make.

Definitely try what @greg and @Alex_Piechowski suggested then:

Connect to your app:

fly ssh console

Echo environment variable:

echo "$MS_URL"

You can also print all environment variables with env.

If the environment variable is not there, it likely means it didn’t get set properly. When an app release breaks after a secret is set, we rollback to the previous version.

I’d like to know the results locally as well.
If there’s any “dotenv” dependency, that could cause the env to be set inside the local app but not in bash

echo “$MS_URL” on local environments prints the url.

I print the url via IO.inspect and then fly logs

  • and it is printed as nil.

I use plain bash script. so i believe this is ruled out.


Error No change detected to secrets

when I tried to set secrets via

fly set secrets MS_URL="$MS_URL"

I guess there are two parts where it could be failing:

  1. setting the secret variable (ie, a blank value is sent to Fly)
  2. getting the secret variable (ie, a blank value is returned from Fly)

So … how about setting a fixed string, using your terminal, outside of any bash script to something e.g:
fly secrets set MS_URL=""
… and what does Fly return?

If Fly says no change detected, well you know it has that value already set (and so the issue must be 2). But if Fly lets you set the secret and so triggers a new release, well then you know it did not already have it (and so the issue must be 1).

Debugging problem - 2) is the problem

fly secrets set MS_URL=""

No change detected is what fly returns.

So the problem is 2)

1) is ok. Verified here.

also, doing fly ssh console , then echo $MS_URL → returns the correct url!

IO.inspect from my code returns nil

where I define System.get_env("MS_URL") , I IO.inspect and it returns nil

Where exactly are you using System.getenv? Could it be using the build ENV to build that value?

Ah, ok, so you know it’s set, it’s definitely the get.

Following on from Alex and Kurt’s ideas, it also sounds to me like that variable is not available at runtime. As you get the value nil, the default, ie “that’s not set”.

What happens with Application.get_env ? Only I was just looking at Elixir: Runtime vs. compile time configuration | AmberBit Sp. z o. o. and that was an idea from Phase 4

Baking the value into a module attribute happens at compile-time, so the System.get_env is going to return nil at build time. Note that building can happen in a remote builder on fly, but can also be built locally, so your secrets won’t be available to the build env. What you want instead is to set the value in your runtime.exs configuration, then read it at runtime in your module. So instead of @base_url, you want to reference base_url() which can be a private function that does:

Application.fetch_env!(:my_app, :base_url)

Or similar. Then you’ll be good to go!


Found examples from your livebeats Repo!

Thank you so much for insights! (on how should I reason above secrets)