Configuring my code with “.env” files
2017-04-18 — 2026-08-09
Wherein the Author Abandons Python’s Dotenv for the Shell-Native Tool Direnv, and Details How Per-Directory .Envrc Files Silently Load and Unload Variables Upon Entering and Leaving Folders.
Assumed audience:
People who run code on multiple machines
Tips for configuring ML and other apps.
There are many tools to load environment variables from local files. A good resource on this is the “Twelve-Factor App” guidance on configuration. But the Twelve-Factor App covers eleven other factors I don’t care about — I’m not a web developer; I only want the environment configuration part.
1 direnv
direnv is a small shell extension that automatically loads and unloads environment variables depending on the directory we’re in. Put a file called .envrc in the project root and direnv will evaluate it whenever we cd into that directory. Leave the directory, and direnv removes those variables from our shell.
1.1 Installation
After installation, hook direnv into the shell so it runs on every prompt:
If installed and hooked up correctly:
1.2 Using .envrc
A minimal .envrc looks like standard bash:
- If we don’t define
RESULTS_DIRin advance, it defaults to/tmp/results. - If we export it manually (
export RESULTS_DIR=/scratch/me), it takes precedence.
We activate it for a given dir with:
This whitelists the .envrc. If I later edit it (or pull changes from git), I must re-run direnv allow. I can force a reload at any time with direnv reload.
There is a dotenv compat layer. Just put dotenv in the .envrc and it will load a .env file in the same directory.
1.3 Benefits
- Variables are set before we run commands in that directory.
- Projects can have distinct
.envrcfiles without clashing. - Defaults can be layered: project-specific defaults, common fallbacks, and user overrides.
1.4 Pitfalls
- Environment is tied to the current directory in interactive shells. If I
cdsomewhere else, the variables automatically unload — which is kind of the point. - Because
.envrcis executable Bash, it can run arbitrary code, so we need to review and explicitly allow it.
In practice, direnv has the simplicity of .env files while integrating with the shell, so configuration is language-agnostic and convenient for both Python scripts and general tools.
2 Python dotenv
One system I’ve used is dotenv. dotenv allows easy configuration through OS environment variables or text files in the parent directory.
There are lots of packages with similar names but dissimilar functions.
Also similar are henriquebastos/python-decouple and sloria/environs. Dynaconf is sophisticated and comes closer to a full configuration system like hydra, and, as such, is too much for me.
Let’s imagine we’re using basic dotenv for now, for concreteness. Then we can be indifferent to whether files come from an FS config or an environment variable.
import os, os.path
from dotenv import load_dotenv
load_dotenv() # take environment variables from .env.
# Code of your application, which uses environment variables (e.g. from `os.environ` or
# `os.getenv`) as if they came from the actual environment.
# substituting a var into a path:
DATA_FILE_PATH = os.path.expandvars('$DATA_PATH/$DATA_FILE')
# getting a var with a default fallback
FAVOURITE_PIZZA_TOPPING = os.getenv('FAVOURITE_PIZZA_TOPPING', 'cheese'),a,32,C
Where “b” is customer name and “32” is temperature, “C” is unit. We wrote a script to loop through the file, but it dont handle when temperature missing. So error’s thrown and script crash’s. Its annoying. We should of added error handling before but we didnt, my bad.
We also provide a CLI; it lets us run arbitrary commands with the correct environment variables set.
This only works for running Python scripts, as far as I can tell. So, why don’t we just use direnv?
