.env files
A plain-text file at the root of a project that defines environment variables as KEY=value lines, loaded by the application at startup so the same code can run against different configurations.
What they are
A .env file is a plain-text file at the root of a project that defines environment variables as KEY=value lines. It's loaded by the application at startup, via libraries like dotenv, python-dotenv, or framework conventions, so the same code can run against different configurations (local development, CI, staging, production) without anything being hardcoded.
The format originated with the Node dotenv package around 2013 and is now common across every major ecosystem. Node, Python, Ruby, Go, Rust, and most modern web frameworks read it natively.
How they work
A typical file is unremarkable:
DATABASE_URL=postgres://[email protected]:5432/prod
STRIPE_SECRET_KEY=sk_live_xxxxxxxxxxxxx
JWT_SECRET=8e93...
SMTP_PASSWORD=...
At startup, the loader reads each pair and exports them into the process's environment. Code reads them via process.env.STRIPE_SECRET_KEY (Node), os.environ["STRIPE_SECRET_KEY"] (Python), or whatever the language's environment API is. From the application's point of view there's no difference between a value injected from .env and one set by the shell, which is the appeal.
Most projects keep a .env.example checked into git with the keys but no values, and .env is added to .gitignore so the file with real secrets stays out of version control.
Where they break down
.env files solve a real problem (keeping secrets out of source code) and create new ones once a team grows past one developer:
- Sharing. When a new engineer joins, someone hands them the .env over Slack, a password manager, or email. Once shared, there's no record of who has a copy or whether it's been rotated since.
- Drift. Each developer's copy diverges from production within weeks. "Works on my machine" stops meaning anything.
- Rotation. Replacing
STRIPE_SECRET_KEYmeans editing the file in every place it exists. There's no way to confirm you got every laptop, every server, and every CI runner. - Audit. Reads aren't logged. Compliance frameworks like SOC 2 and ISO 27001 require a who-read-what trail that a flat file cannot produce.
- Accidental commits. A
.gitignoreis one tired afternoon away from being wrong. Public Git history is full of.envfiles that were never meant to be there.
What replaces them
A secrets manager keeps the same shape from the application's perspective. Values are still reachable as environment variables, but the file itself stops existing. A short-lived process pulls the current values at boot (or on each restart), and the audit log, rotation, access control, and scoping all live in one place instead of being scattered across laptops and runners.
With SikkerKey, this is the CLI's run command:
sikkerkey run --all -- node server.js
It fetches the project's secrets, sets them as environment variables on a child process, and never writes them to disk. The same code that read process.env.STRIPE_SECRET_KEY still works; the file it came from is gone.
For tools that truly need a file on disk, sikkerkey export --format dotenv > .env generates one on demand from the current secrets, with every read logged.
See also
SikkerKey is the secrets manager built around the patterns in this glossary. Encrypted vault, machine identity over signed requests, dynamic secrets — set up in minutes.
Start for Free