turboEnv

Managing environment variables across development, staging and production

Most teams start the same way. One .env file, checked into .gitignore, passed to new hires over Slack. It works until roughly the third environment or the fourth developer, and then it starts costing an afternoon a week in ways that are hard to attribute.

This is a description of how that failure actually unfolds, and what to insist on when you replace it.

The three failures

Drift. Someone adds STRIPE_WEBHOOK_SECRET to staging to test a feature. It is never added to production, because production is configured through a different interface — a dashboard, a Terraform variable, a Kubernetes secret. The deploy succeeds, because a missing environment variable is not a build error. It becomes a runtime error, in production, on the one code path that reads it.

No history. Something breaks at 2am and the last deploy did not change any code. Did the configuration change? With a .env file, there is nothing to answer that question. There is no diff, no author, no timestamp. You are reduced to asking people what they remember doing.

Onboarding by archaeology. A new developer clones the repository and needs values for fourteen variables. .env.example lists nine of them, because the last five were added by people who did not update it. They spend their first day asking which values are real.

Each is a small tax. Together they are why configuration is quietly one of the most common causes of production incidents.

What to insist on

Whatever you replace it with — a hosted secrets manager, a self-hosted one, or something you build — these are the properties that actually matter, roughly in order.

One source of truth per environment, reachable at runtime. The value your application uses must come from the same place the value you edited went. Any design where a human copies a value from one system into another reintroduces drift immediately, because the copy is a step that can be skipped.

Versioning with an author and a message. Not an audit log of API calls — the actual before-and-after of the values. When you ask "what changed", the answer should be a diff, and rolling back should be one action rather than a reconstruction from memory.

Encryption at rest, with an honest description of the boundary. Almost everything says "encrypted". The question worth asking is who holds the key. If the provider holds it, they can read your values, and no amount of marketing language changes that. That may be perfectly acceptable — it is the same trust you already extend to your database host — but you should know which you have bought.

Scoped credentials. The token your production deployment uses should read production and nothing else. If one credential reads every environment, a leak from your least protected environment is a leak from your most protected one.

A boring failure mode. Your applications now depend on a network call at startup. Ask what happens when that call fails during an incident. A client that caches its last successful response and boots from it is the difference between a degraded dependency and a total outage. This one is easy to overlook when everything is healthy.

What it looks like in practice

The mechanics should be unremarkable. A process starts, presents a credential scoped to one environment, receives that environment's variables, and runs:

npx turboenv-cli run -- node server.js

Locally, the same command with a development token. In CI, the same command with a token from the pipeline's own secret storage — one secret to manage there instead of forty. Nothing in your application code changes, because the variables arrive in process.env exactly as they would have from a file.

The .env file does not disappear entirely, and it should not. Keep .env.example for documentation of which variables exist. It just stops being the thing that holds real values.

The part that is not technical

The reason .env files persist is not that engineers are unaware of the alternatives. It is that most alternatives ask for a meaningful setup cost — a Vault cluster to operate, an IAM policy to reason about, a per-seat bill to justify — in exchange for solving a problem that feels small right up until it isn't.

So the honest advice is to pick the least ceremonious thing that has the properties above. turboEnv is one option, and it is deliberately narrow: it stores environment variables, versions them, encrypts them, and hands them back. It does not do secret scanning, rotation scheduling, or dynamic credentials. If you need those, you want Vault, and the extra ceremony is the price of the extra capability.

What you should not do is keep passing files around and hoping the next person remembers to update staging.