Dotenvy.source
source
, go back to Dotenvy module for more information.
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 implementsDotenvy.parse/3
callback. Default:Dotenvy.Parser
:require_files
specifies which of the givenfiles
(if any) must be present. Whentrue
, all the listed files must exist. Whenfalse
, 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'sfiles
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 tofalse
to disable a side-effect altogether, but this will effectively neutralize theenv!/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 tosource/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 theLOG_LEVEL
variable would be set todebug
, 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 includeSystem.get_env()
as the final input tosource/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.