The .env Nightmare: Why Config Files, Environment Variables and Recursive Config Servers All Need to Die
Environment variables had one job
Your entire project configuration is currently scattered across a .env file, three appsettings.json files, a config.yaml in the root, another one in ~/.config, and a Kubernetes ConfigMap that nobody remembers how to update.
And somehow this is the default that every LLM on the planet reaches for when you ask it to scaffold a project.
Let me explain why this makes me angry.
What Actually Happens With Environment Variables
Environment variables had a legitimate job in the 1970s. They were shell-level knobs. PATH. HOME. TERM. Simple text values that told the operating system which way was up. Fine.
Then the twelve-factor app manifesto showed up and elevated ENV to a religious principle. Store config in the environment. Never in the code. Amen.
And somewhere along the way, the industry decided that meant a flat text file full of KEY=VALUE pairs was the peak of configuration engineering.
It is not.
It is lazy.
No Types. No Validation. No Structure.
Everything is a string. Your integer? String. Your boolean? String. Your comma-separated list of connection strings? Also a string, good luck parsing it consistently across every consumer.
Nobody knows if DATABASE_URL is supposed to have a port or not until the app starts and falls over.
You cannot nest. You cannot group. You cannot express that DB_HOST, DB_PORT, DB_USER, DB_PASS belong to a logical block called database. Nope. Flat list. Alphabetical chaos.
Half the .env files in existence contain production secrets checked into repos that should never have seen them. GitHub's secret scanner is playing whack-a-mole against a pattern the industry refuses to stop using.
Environment variables are all runtime, which means you cannot statically analyse what your application expects. You discover missing variables at 2am when the container refuses to start.
And Config Files Are Not Better
The argument usually goes: "Okay, fine, .env is terrible. I will use structured config files instead."
Great. Congratulations. You graduated from a sticky note to a folder full of sticky notes.
Now you have appsettings.json, appsettings.Development.json, appsettings.Staging.json, appsettings.Production.json, database.config.json, secrets.json that is definitely not checked in (except it is, in three branches), and a docker-compose.override.yml that overrides half of them with environment variables anyway.
Every service has its own config file format. Every team has its own opinion about where config files live. Every deployment pipeline has its own way of injecting them.
Config files rot on disk. They get copied between environments by accident. They accumulate orphaned keys that nobody is brave enough to delete. They version poorly. They drift. They lie.
A config file that sits on a filesystem is a config file you cannot trust.
What Should You Actually Do
Stop putting config on disk altogether.
If it is configuration, it belongs in a config store. That is what config stores exist for. That is literally the problem they solve.
Consul. etcd. Spring Cloud Config. Azure App Configuration. AWS AppConfig. Pick one. They all do the same thing: config lives in one place, it is versioned, it is audited, it is served over the network, and your application asks for it at startup.
If the config store is down, your application should not start. That is a feature, not a bug. You want to fail fast when you cannot reach config, because if you cannot reach config you have no idea what environment you are running in or what it is supposed to do.
If You Do Not Like Secrets Managers, At Minimum Use Redis
Redis is everywhere. It is already in your stack. It is fast. It is dead simple. Push a config key into Redis, pull it out at startup. Done.
Is Redis a production-grade config store? Honestly, it depends on your definition of production grade. But it is infinitely better than a text file on a server that someone SSHes into to edit with vim and hopes for the best.
A Redis key is retrievable. A config file on a jump box is a prayer.
The Pattern That Makes Me Want To Throw Things
There is a pattern that has infected enterprise software over the last decade that deserves a special place in architectural hell.
The application starts. It reads a config file to find out where the config server is. It connects to the config server. The config server tells it where the secrets manager is. It connects to the secrets manager. The secrets manager returns the actual configuration values.
Think about what just happened.
Your application, which has no configuration, read a file to find config, in order to find secrets, so it could finally get the configuration it needed to actually do its job.
That is not distributed systems engineering.
That is recursion wearing an enterprise architecture badge.
Your config server has the config. It decides what environment you are in. It decides whether you asked for prod or test. It decides what secrets to serve. The application does not need to know any of that. The application needs to ask one question -- "Who am I?" -- and the config server answers with everything the application needs to run.
If your config server needs another config server to start, you have built a Chinese puzzle box, not an infrastructure layer.
What Every LLM Gets Wrong
Ask any LLM to build you a web app. Go on.
"What database connection string pattern should I use?"
Every single time:
DATABASE_URL=postgresql://localhost:5432/mydb
SECRET_KEY=supersecretkey123
API_KEY=your-api-key-here
It is baked into the training data so deeply that it has become an architectural reflex. The model does not consider alternatives because the model was trained on a million tutorials that all copy the same bad pattern.
LLMs do not learn from production experience. They learn from the average of everything that has ever been written. And the average of everything ever written is an undergraduate tutorial that shoves everything into .env and calls it a day.
The result is that LLMs propagate the worst configuration pattern in modern software as if it is the only option.
They will scaffold your entire microservice architecture with .env files, config.yaml files scattered across every repo, and absolutely no mechanism for centrally managing, validating, or auditing any of it.
Then they will tell you it is "production ready."
The Bottom Line
Environment variables are fine for shell-level overrides and container orchestration injection points.
Config files are fine for local development where you need something that works without a network call.
Neither of them is a configuration management strategy.
Put your config in a config store. Put your secrets in a secrets manager. Make your application ask one question and get one answer. If the store is down, the application does not start -- and that is correct behaviour, because running with stale or missing config is worse than not running at all.
Stop letting LLMs scaffold your projects with .env and config.yaml as if configuration engineering peaked with Docker Compose.
Your future self, debugging a production incident at 3am because the config file on the staging server was actually the prod config with different variable names, will thank you.