env-master

Why .env files don't belong in production

Short answer: a .env file on a developer laptop is a handy format for running things locally, but a .env file on a production server is a secret stored in plaintext on disk, whose copies you no longer control. It ends up in backups, image layers, CI artifacts and home sync folders; any process on the host can read it; and when it leaks, the file itself will not tell you. The fix is not “hide the file better” but removing it from the deploy entirely: at startup the service fetches its values from a secrets manager under its own machine identity, and every such read stays in the log.

A file on disk cannot keep quiet

The problem with .env in production is not the format — it is the properties of a file as a medium:

  • Copies multiply on their own. The file lands in host backups, disk snapshots, a Docker image layer if .dockerignore missed it, and CI artifact archives. Deleting the original does not delete the copies.
  • Any process on the host can read it. Mode 600 protects against other users, not against a process running as the same one — which is exactly who runs next to your application.
  • A file does not record its readers. Nobody knows who opened it and when. Incidents get investigated from circumstantial evidence, if at all.

“We use environment variables, not a file” is not an answer

Variables set in an orchestrator or CI get rid of the file, not the problem. A value in the process environment is visible through /proc/<pid>/environ to any process with the right privileges, leaks into crash dumps and error reports, and shows up in the orchestrator UI for everyone with project access. Above all, it is still handed out by a human and stored by a third party (the CI, the PaaS) in the clear: the secrets manager is replaced by deploy settings that have neither read auditing nor rotation.

What delivery without a file looks like

In env-master a service is a first-class entity with its own machine identity. At startup the SDK authenticates with client credentials, gets a short-lived token and fetches its values — ciphertext plus a key wrapped for that specific identity. Decryption happens in the service's own memory: the machine path is honestly client-side, and the server never produces the plaintext in this scenario (where the boundary actually lies is written on the security page and in the threat model — without the words “zero-knowledge”).

From then on the values live only in process memory: not on disk, not in the image, not in CI settings. Every read lands in the audit log, an identity can be scoped down to a list of keys (DB_*, not the whole environment), and a value marked restricted is never shown to a person at all — only the service fetches it.

How to migrate without stopping the service

Moving off .env does not take a rewrite in one evening:

  1. Create a project and move the values. envmaster import .env --dry-run shows the plan before writing anything and flags what looks like a secret (see the quickstart).
  2. Issue the service an identity scoped to its environment and key set.
  3. Connect the SDK — it replaces reading process.env with a client that keeps values in memory and renews its token itself.
  4. An intermediate step for legacy: envmaster run -- <command> hands values to a child process through the environment without writing them to disk — the application needs no changes at all.
  5. Delete the file from servers, CI and backups — nobody needs it anymore.

To make sure nothing was lost along the way, schema validation checks required keys against what the store actually holds and stops a deploy before runtime, not after.

The emergency exit always stays open: dumping an environment back into a plain .env is one CLI command. A store you cannot leave is not a store — it is a hostage situation.

← All articles