Loading dotenv files

View Source

This page describes different scenarios for using the dotenv!/1 function.

Dotenv files should always be loaded from your config/runtime.exs file. You may have to create it yourself if it does not exist.

Loading a single file

This is the classic dotenv experience.

# config/runtime.exs

# Import the Config and Nvir modules
import Config
import Nvir

# Load your dotenv file
dotenv!(".env")

# Start configuring your application
config :my_app, MyApp.Repo,
  username: env!("DB_USERNAME", :string!),
  password: env!("DB_PASSWORD", :string!),
  database: env!("DB_DATABASE", :string!),
  hostname: env!("DB_HOSTNAME", :string!),
  port: env!("DB_PORT", :integer!, 5432)

Loading from different sources

The Nvir.dotenv!/1 function accepts different types of sources to define which dotenv files to load.

The sources can be different types of values like lists, nested tuples, etc., but all of them must finally contain a file path.

Nvir accepts relative paths or absolute paths. Relative paths are relative to File.cwd!(), which is the directory containing mix.exs.

See the custom loaders documentation to change the relative path target.

Loading multiple files

You can pass a list of different files to the dotenv!/1 function.

Nvir ignores the files that do not exist: your .env file will likely not be present in production, and you may have a .env.test file but no .env.dev file

dotenv!([".env", ".env.#{config_env()}", ".env.local"])

Tagged sources

Nvir has a concept of enabled or disabled sources. This works by wrapping the dotenv paths in tagged tuples.

This gives you more control over the files that are loaded, and ensures that no file will be loaded in production if the dotenv files are committed to Git by mistake and/or included in your releases.

In this example, a different file is loaded depending on the current Mix environment.

dotenv!(
  dev: ".env",
  test: ".env.test"
)

It is also valid to pass the same key multiple times:

dotenv!(
  dev: ".env",
  test: ".env.test",
  test: ".env.test.local"
)

Predefined tags

Those tags are defined automatically by Nvir based on the current environment.

See the custom loaders documentation to know how to define your own tags.

Mix environment

  • :dev - When Config.config_env() or Mix.env() is :dev.
  • :test - When Config.config_env() or Mix.env() is :test.

There is no predefined tag for :prod. Using dotenv files in production is an anti-pattern. The guide on custom loaders will help you if you really need to.

Continuous integration

  • :ci - When the CI environment variable is "true". This variable is defined by most CI services.
  • :ci@github - When the GITHUB_ACTIONS environment variable is "true".
  • :ci@travis - When the TRAVIS environment variable is "true".
  • :ci@circle - When the CIRCLECI environment variable is "true".
  • :ci@gitlab - When the GITLAB_CI environment variable is "true".

Operating system

  • :linux - On Linux machines.
  • :windows - On Windows machines.
  • :darwin - On MacOS machines.

Nested sources

List and tuple source may contain other nested sources, they are not limited to paths.

dotenv!(
  dev: ".env",
  test: [".env.test", ".env.test.local", ci: ".env.ci"]
)

In this example, the :test tag contains another list, and one element of this list is a :ci tagged tuple.

If you are not familiar with Elixir's keyword lists, the following is an equivalent without the syntactic sugar.

dotenv!([
  {:dev, ".env"},
  {:test, [".env", ".env.test", {:ci, ".env.ci"}]}
])

Overwrite mechanics

The files loaded by Nvir will not replace variables already defined in the real environment.

That is, as your HOME variable already exists, defining HOME=/somewhere/else in a dotenv file will have no effect.

A special tag can be given to dotenv!/1 to overwrite system variables:

dotenv!([".env", overwrite: ".env.local"])

With the code above, any variable from .env that does not already exist will be added to the system env, but all variables from .env.local will be set.

Just like any source tag, the :overwrite key accepts any nested source types. The following forms are equivalent:

dotenv!(
  dev: [".env.dev", overwrite: ".env.local.dev"],
  test: [".env.test", overwrite: ".env.local.test"]
)

dotenv!(
  dev: ".env.dev",
  test: ".env.test",
  overwrite: [dev: ".env.local.dev", test: ".env.local.test"]
)

In :dev environment, the two snippets above would both result in loading .env.dev then .env.local.dev.

Nesting :overwrite tags has no effect. All sources nested in an :overwrite tag are considered overwrites. In the following snippet, all files except 1.env are overwrite files.

dotenv!(
  dev: "1.env",
  overwrite: ["2.env", dev: ["3.env", overwrite: "4.env"]]
)

The 3.env file is considered wrapped in an :overwrite tag, albeit indirectly. The :overwrite tag around 4.env is useless.

Load order

The dotenv!/1 function follows a couple rules when loading multiple sources:

  • Files are separated in two groups, "regular" and "overwrites".
  • Within each group, files are always loaded in order of appearance in the sources list. This is important for files that reuse variables defined in previous files.
  • The "regular" group is loaded first. The files from the "overwrite" group will see the variables defined by the "regular" group.

The order of execution is the following:

  • Load all regular files in order.
  • Patch system environment with non-existing keys.
  • Load all overwrite files in order.
  • Overwrite system environment with all keys.

This means that the following expression will not load and apply .env.local first because it belongs to the "overwrite" group, which is applied last. But .env1 will always be loaded before .env2.

dotenv!(overwrite: ".env.local", dev: ".env1", dev: ".env2")

In the following example, files are named in load order (without regard for enabled or disabled tags).

dotenv!(
  dev: "1",
  test: ["2", ci: "3", overwrite: "100"],
  overwrite: ["101", test: "102"],
  linux: "4"
)

The load order impacts variable interpolation and inheritance for variables that are repeated in multiple files. Please refer to the Variable Inheritance guide.