Dotenv, Makefile & rules for a nice DX
You must already be aware of the Twelve-Factor App, and I want talk about the 3rd one:
III. Config
Store config in the environment
This has been widely adopted and this is a great practice.
However, the .env
file has also been adopted to configure those variables. It creates some frustrating and unsuspected behaviors.
I've done my fair share of mistakes in the past with these environment variables and after going back and forth, I established a list of rules.
Rules
Rule #1: Always store configuration in the environment variables
This one is obvious, with the tooling we have today to deploy applications (k8s, docker, etc), it's crazy not to use environment variables for the configuration.
Rule #2: Allows developers to use a .env
file
It's a good reminder that your tooling should support a .env
file and use it, when there is one.
Rule #3: Never include the .env
file in the versionning
Instead, you can keep a .env.dist
in your versionning.
Rule #4: Avoid conditional .env
files (.env.local
, .env.dev
)
It's working, but it will over-complexify the system and each of your tool needs to support it.
Rule #5: Existing environment variables must have precedence over values in .env
This is a tricky one. It seems logical, but we need to carefully check if this is really the case.
Makefile, for example, does not play nice with this one.
Rule #6: Automatically check if all required environment variables are at least declared
Before starting your application, you should check if the required variables are defined.
Makefile
Since make ignores .env
by default (and for good reasons), I've added this hack in my Makefiles:
Loading variables in the right order
I want the variables to be loaded in this order, from highest priority to lowest priority:
- real environment variables
.env
Makefile
default valuesdocker-compose.yml
default values
To test the correct order of precedence of my solution, I've created this repository.
With this setup, I can override any default variable from docker-compose.yml or Makefile in my local .env
.
At the same time, If I need to run a target only once with a different variable, I can still do it like this: FOO=bar make up
.
This is really a Developer eXperience oriented decision, I want developers of a project to have control over the way it's configured locally. With any decent CI, it's not an issue.