Dotenvy.source

You're seeing just the function source, go back to Dotenvy module for more information.
Link to this function

source(files, opts \\ [])

View Source

Specs

source(inputs :: input_source() | [input_source()], opts :: keyword()) ::
  {:ok, %{optional(String.t()) => String.t()}} | {:error, any()}

Like its Bash namesake command, source/2 accumulates values from the given input(s). The accumulated values are stored via a side effect function to make them available to the env!/2 amd env!/3 functions.

Think of source/2 as a merging operation which can accept maps (like Map.merge/2) or paths to env files.

Inputs are processed from left to right so that values can be overridden by each subsequent input. As with Map.merge/2, the right-most input takes precedence.

Options

  • :parser module that implements Dotenvy.parse/3 callback. Default: Dotenvy.Parser

  • :require_files specifies which of the given files (if any) must be present. When true, all the listed files must exist. When false, none of the listed files must exist. When some of the files are required and some are optional, provide a list specifying which files are required. If a file listed here is not included in the function's files argument, it is ignored. Default: false

  • :side_effect an arity 1 function called after the successful parsing of each of the given files. The default is an internal function that stores the values inside the application process dictionary. You may also set this value to false to disable a side-effect altogether, but this will effectively neutralize the env!/2 function.

Examples

The simplest implementation is to parse a single file by including its path:

iex> Dotenvy.source(".env")
{:ok, %{
  "TIMEOUT" => "5000",
  "DATABASE_URL" => "postgres://postgres:postgres@localhost/myapp",
  # ...etc...
  }
}

More commonly, you will source multiple files (often based on the config env) and you will defer to pre-existing system variables. The most common pattern would do something like the following:

  iex> Dotenvy.source([
    "#{config_env()}.env",
    "#{config_env()}.override.env",
    System.get_env()
  ])

In the above example, the prod.env, dev.env, and test.env files would be version-controlled, but the *.override.env variants would be ignored, giving developers the ability to override the values defined in the version-controlled files.

Give Precedence to System Envs! {: .warning}

Don't forget to include System.get_env() as the final input to source/2 so that system environment variables take precedence over values sourced from .env files.

When you run a shell command like LOG_LEVEL=debug mix run, your expectation is probably that the LOG_LEVEL variable would be set to debug, overriding whatever may have been defined in your sourced .env files. Similarly, you may export env vars in your Bash profile. This doesn't happen automatically: you must explicitly include System.get_env() as the final input to source/2.

If your env files are making use of variable substitution based on system env vars, e.g. ${PWD}, then you would need to include System.get_env() before your inputs.

For example, if your .env references the system HOME variable:

  # .env
  CACHE_DIR=${HOME}/cache

then your source/2 command would need to make the system env vars available to the parser by including them as one of the inputs, e.g.

  iex> Dotenvy.source([System.get_env(), ".env"])

Including the System.get_env() before your files means that your files have final say over the values, potentially overriding any pre-existing system env vars. In some cases, you may wish to reference the system vars both before and after your own .env files, e.g.

  iex> Dotenvy.source([System.get_env(), ".env", System.get_env()])

or you may wish to cherry-pick which variables you need to make available for variable substitution:

  iex> Dotenvy.source([
    %{"HOME" => System.get_env("HOME")},
    ".env",
    System.get_env()
  ])

This syntax favors explicitness so there is no confusion over what might have been "automagically" accumulated.