Enforce Proper direnv Setup

2025/08/14

Two direnv extensions which help ensure a project's environment variables are properly set up

Why direnv?

I prefer to use direnv in development rather than .env files because direnv provides configuration via the environment itself, rather than via a system that has to be explicitly loaded. The application knows nothing about how its environment is set up.

My Requirements

I want any project I create to have a template for setting up development environment and checks that things stay aligned.

First, I add a note to every project's README to indicate that direnv is required in development.

Any developer who works a project configured this way will need to set it up.

Once they've got that working, the developer (who may be myself months later!) needs to know what values to set and what they mean.

I achieve this by through the combination of .envrc and .envrc.private and the use of two direnv functions that check that everything's been set up correctly.

.envrc

All my projects have an .envrc file which is included in the repo and which look like this:

.envrc:
# SOME_REQUIRED_VARIABLE: some variable that must be set for the application to run
export SOME_REQUIRED_VARIABLE=
# SOME_OTHER_VAR: (optional) a variable you don't necessarily need to set
export SOME_OTHER_VAR=
# A_VARIABLE_WITH_A_DEFAULT: true*|false - a variable that has a default
export A_VARIABLE_WITH_A_DEFAULT=true

source_env .envrc.private

if has ensure_all_documented; then
  ensure_all_documented
else
  echo "No ensure_all_documented function found, skipping documentation check. Install from here: https://raw.githubusercontent.com/joeyates/dotfiles/refs/heads/main/.config/direnv/direnvrc"
fi

if has ensure_all_set; then
  ensure_all_set
else
  echo "No ensure_all_set function found, skipping environment variable check. Install from here: https://raw.githubusercontent.com/joeyates/dotfiles/refs/heads/main/.config/direnv/direnvrc"
fi

All the environment variables that the application relies on to be set are included, possibily with default values. Each variable has documentation, which can also indicate if the variable is optional.

The Checks

The two functions, ensure_all_documented and ensure_all_set are available from my dotfiles repo.

Note that nothing breaks if you don't have them, you just get a nagging message every time you run direnv allow to update things.

ensure_all_set checks that all the environment variables set in .envrc have been given a value.

ensure_all_set is an automatic version of direnv's own env_vars_required. But, unlike env_vars_required, it doesn't require a list of the variables to check - it checks evry on that is declared, so I believe it's less error prone. You can opt out of the check by putting (optional) in the documentation string.

ensure_all_documented checks that all the environment variables in .envrc have a preceding comment matching variable name + ": " + some text. If they don't, it prints a warning in red:

<code>ensure_all_documented</code> gives a warning in red if a variable is declared without documentation
ensure_all_documented gives a warning in red if a variable is declared without documentation

.envrc.private

.envrc.private, is excluded from the repo (via .gitignore) and is expected to be present by .envrc, if it's not there, direnv gives a warning:

direnv: referenced .envrc.private does not exist

So, that message will prompt the developer into creating .envrc.private.

This file needs to set the values from .envrc without defaults:

.envrc.private:
export SOME_REQUIRED_VARIABLE=foo

Conclusion

When a project configured in this way, setup is quite linear: README -> direnv -> create .envrc.private. Plus, there's an opt-in for enforcement.

As things change during development, the checks ensure environment variables are set properly, and that future onboarding remains well documented.