Getting Started
View SourceThe concept of environment variables is simple and Dotenvy
aims to make your application take advantage of them, but how can you start using them easily in your application? This page will walk you through kicking the tires of a simple application so you can learn how Dotenvy
works.
Prerequisite
If you haven't already, install the
dotenvy_generators
.When you run
mix help
, you should seedot.new
as one of the available tasks. Make sure that's available before continuing.
Generating an app
The dot.new
mix task is available when you have installed the dotenvy_generators
. We can use it generate a new Elixir app:
mix dot.new example
Follow the prompts given:
cd example
mix deps.get
The structure should look familiar to you -- the only thing you might notice is the presence of the envs/
directory.
Environment-specific env files
Take a look at the config/runtime.exs
. It includes a line like the following which reads from an environment variable named SECRET
:
config :example, :secret, env!("SECRET", :string!)
If you start your app using iex -S mix
and enter into the iex
shell:
iex> Application.get_env(:example, :secret)
"my-secret-dev"
This value came from the envs/.dev.env
file, which declares the following:
SECRET=my-secret-dev
Next, let's try running a test. Open up the test/example_test.exs
file and edit it so we have single test like the following:
test "reads variables specific to an env" do
assert "my-secret-dev" == Application.get_env(:example, :secret)
end
Then run mix test
.
Uh oh! The test fails because Application.get_env(:example, :secret)
returned "my-secret-test"
and not "my-secret-dev"
.
Take a look at the envs/.test.env
file and see how the variable is declared there. We can now adjust our test so the assertion matches the value we declared in our .test.env
:
test "reads variables specific to an env" do
assert "my-secret-test" == Application.get_env(:example, :secret)
end
Running mix test
now passes!
Core Concept: variables are read from an environment-specific file
Just like with Elixir's regular config files, Dotenvy
loads the appropriate
env file depending on your environment. Look at how the config_env()
function
is used in runtime.exs
to determine the file name; different values are
declared in the .test.env
and .dev.env
.
Environment Variables
Next, let's try to access the environment variable directly:
iex> System.get_env("SECRET")
nil
What happened? Application.get_env(:example, :secret)
worked, so why doesn't System.get_env("SECRET")
see the variable?
The answer to this riddle is that Dotenvy
is read-only: Dotenvy
does not set environment variables. This helps keep things locked down. It may be counter-intuitive, but Dotenvy
doesn't even necessarily read environment variables! Dotenvy
only reads the inputs you give it. Dotenvy
only reads environment variables if you pass it the output from System.get_env()
.
Core Concept: Dotenvy
does not set ENV vars
Any variables you declare in your from in your env
files are not exported
back to the system; i.e. System.put_env/2
is NOT called. In other words,
declaring a variable FOO
in one of your parsed .env
files does not mean
System.get_env/2
can be used to retrieve it later. This encapsulation is by design!
If you want to set environment variables, you must do it explicitly.
Establishing a contract
One of the tenets of the 12-factor App is to have a clean contract with the underlying operating system, offering maximum portability between execution environments. Our runtime.exs
is largely responsible for this: it dictates exactly which variables it needs.
To see this in action, let's add another configuration setting to our app by adding the following line to our example runtime.exs
:
config :example, :password, env!("PASSWORD", :string!)
Then stop and restart your app by pressing ctrl-C and running iex -S mix
once more. You will see an error:
** (RuntimeError) Environment variable PASSWORD not set
The application is declaring its contract by specifying that certain environment variables must be present. Because the PASSWORD
variable is not set, the application will not start because the contract has not been met.
We can provide the variable on the command line:
PASSWORD=xxxx iex -S mix
And the app will start normally.
Alternatively, you can provide this value in your env files. Add the following to your envs/.dev.exs
file:
PASSWORD=xxxx
Your application will now start in the dev environment. However if you try to run tests, you will once again see the RuntimeError
. You can rectify this by supplying a value in the envs/.test.dev
file.
A good convention here is to have a default .env
file loaded first which lists all the variables that your app needs. That's a great place to put some documentation too!
Core Concept: your app should dictate which variables it needs
if your application needs certain configuration values to run, then it should demand that those values are set. The implication is that if those values aren't there, there's no point in starting the app because it can't do what it needs to do. This is the contract with the environment.
Type-casting
All environment variables store string values. Dotenvy.env!/2
and Dotenvy.env!/3
have as their second argument an atom which determines how to convert the string value. For example, you may need to convert a PORT
variable into an integer, other values may need to be booleans, and others may need to be atoms or modules.
There is some subtlety involved here when it comes to how empty values should be handled.
Let's revisit our PASSWORD
variable from the previous section. Let's try setting the environment variable to an empty value before we start our app:
$ PASSWORD= iex -S mix
** (RuntimeError) Error converting variable PASSWORD to string!: non-empty value required
Uh-oh! This causes another error, this time not because the variable wasn't set (it was), but because its value was empty.
Let's modify the line in our runtime.exs
and replace :string!
with :string
(i.e. remove the exclamation point):
config :example, :password, env!("PASSWORD", :string)
Now the application starts fine, even if the PASSWORD
value is empty. Probably a password needs to have a value, so using :string!
for the second argument is probably more appropriate, but you can decide this on a case-by-case basis.
Understanding type-casting is another core concept in helping to leverage Dotenvy
so your app get what it needs to run.
Core Concept: type-casting
For each variable you read via Dotenvy.env!/2
in config/runtime.exs
, you
should consider what the resulting Elixir value needs to be. Can the value be empty? Are
nil
values allowed? Choose the conversion type
that best supplies your app with the value it needs.
See the section on releases for further information on how Dotenvy
works in the context of a Mix release.