Dynamic configuration and edeliver upgrades

I’m happily using distillery and edeliver but just ran into a problem with upgrades. A lot of my application’s env variables are set in the start/2 function of lib/my_app.ex since they’re dynamic:

defmodule MyApp do
  use Application

  def start(_type, _args) do
    import Supervisor.Spec, warn: false

    Application.put_env(:my_app, :pidfile, System.get_env("PIDFILE"))
    Application.put_env(:my_app, :secret_thing, System.get_env("BLAH_BLAH"))

After upgrades, when I run Application.get_all_env(:my_app) I find that the only variables that are set are those that were statically defined in mix.exs (and prod.exs).

The upgrade has effectively removed the dynamic configuration that runs during start/2. I’m guessing this will only betray my ignorance around how upgrades actually work, but is there some simple explanation as to why the application envs get “un-set” and does anyone have an idea for how to use upgrades while still setting application envs dynamically from a .env file on my production server?

In case it’s useful for the diagnosis, or someone else’s benefit, I use systemd to start the app with local ENV variables:

Description=MyApp Platform




ExecStart=/home/my_app/app_release/my_app/bin/my_app start
ExecStop=/home/my_app/app_release/my_app/bin/my_app stop


Application.put_env/4 has a persistent: true option to make the value outlive upgrades https://hexdocs.pm/elixir/Application.html#put_env/4


You could also implement the config_change/3 function in your application’s module; it gets called when the config has changed (after an upgrade); so there you can re-initialize / call put_env again.

(for some reason I don’t see this info in the Elixir docs, but I know it works, the Erlang docs have it).


Thank you @michalmuskala and @arjan . Weirdly, I’m not having luck with persistent: true and I’m not sure that I’ve implemented config_change/3 correctly. Right now, my config_change looks like this:

def config_change(changed, new, removed) do
    MyApp.Endpoint.config_change(changed, removed) 

Should I be explicitly handling each of these changes? Maybe doing something like this?
def config_change(changed, new, removed) do

def config_change(changed, new, removed) do
    MyApp.Endpoint.config_change(changed, removed) 

    # HACK: This feels a bit clunky...
    Enum.each(changed, fn(x) ->
      Application.put_env(:my_app, elem(x, 0), elem(x, 1))

    Enum.each(new, fn(x) ->
      Application.put_env(:my_app, elem(x, 0), elem(x, 1))

    Enum.each(removed, fn(x) ->
      Application.delete_env(:my_app, elem(x, 0), elem(x, 1))


Thanks again for the swift responses. I must admit that these now actually seem like separate issues—I’m baffled as to why the config vars added with persistent: true get destroyed on the upgrade, and feel as though the implementation of config_change should only work with env vars that actually change. We roll out front-end changes daily, but rarely ever change the application config. For context, I started down the release upgrade path so that we could keep making minor enhancements to the front-end while not creating any downtime with our Phoenix app which is responsible for keeping an endpoint available so that folks can post data to it at any time.

Hi folks, I’m still not able to get my dynamic configuration (those vars set during start/2) to either persist OR reload properly after an upgrade. Does anyone have an example of how they’re using:

  1. Application.put_env/4 with persistent: true
  2. config_change/3

Any examples would be greatly appreciated! As mentioned in my previous post, I’ve followed the docs as best I can to no avail.