One store for every .env in the project.
dotenv reads a file into process.env, which is
all most projects need. This is for the ones with four files, three
machines, and a credential that gets rotated.
+ catalog created .envs/catalog.sqlite
+ added to .gitignore .envs/ Recovery codes — 5, shown once, not stored
1575H-MPBMP-K41AR-PXAVB-NN920-G
G8T2M-4KQZR-7VXWD-3NBHE-J05YA-P
… envs load .env
+ .env 12 keys
+ release 8f21ac04 is current 12 values envs run -- node server.js
- running with 12 values node
The values live in a SQLite catalog instead of a file. That is what lets
envs doctor name the file a value came from, and makes
envs rollback a pointer move rather than a re-import.
What the store buys you
Which file set this key
When .env and .env.local both declare
DATABASE_URL, envs doctor names the one
that won. It prints the key and the source. It never prints the
value.
One secret under two names
DB_URL and DATABASE_URL holding the same
value is invisible to anything that compares names, and it is not a
duplicate — it is a second copy free to drift. Rotate one and the
other still holds what you retired. envs doctor names
both keys and, as always, neither value.
Losing the key is survivable
init prints five recovery codes once. Each wraps the
same data key, so any of them opens the catalog on its own, and the
catalog stores only the wraps.
A file that isn't env format gets refused
dotenv parses a two-line YAML document into environment
variables. envs checks the file first, and if it fails,
nothing loads at all.
Rollback is a pointer move
Releases are immutable. Changing a value writes a new release;
envs rollback moves the current pointer back and
deletes nothing.
Point dotenv at the wrong file
It does not fail. It hands back keys and values, and the process starts with the wrong configuration. Measured against dotenv 17.4.2:
| Input | dotenv | envs |
|---|---|---|
name: envs over two lines |
parses into keys | rejected |
KEY: value |
{KEY: "value"} |
rejected |
A="oops, rest valid |
keeps going | rejected |
{"a": 1} |
{}, silently |
rejected |
Every input we measured, and the rule envs uses
Migrating from dotenv
config() takes the same options: path,
encoding, override, processEnv.
It is synchronous, so the side-effect import still works.
- import "dotenv/config";
+ import "@modootoday/envs/config";
The command names follow dotenvx: run -- cmd,
get, set, ls,
rotate. If you have used it, most of this is already
familiar.
Three reasons you might not want this
The licence is not OSI open source
Elastic License 2.0: use it inside your company at any scale, ship it in a product you sell, fork it. What you cannot do is offer it to third parties as a hosted service. If your policy requires an OSI licence, dotenvx is the better choice.
Startup costs about 6.6 ms
That is for a hundred keys. dotenv resolves the same set in 0.2 ms, because it decrypts and authenticates nothing. If that gap matters in your process, stay on dotenv.
One file does not need a store
A single .env and one developer is what dotenv is for.
No key to manage, nothing to back up.
No account. The catalog is a file in your project.
envs makes no network call unless you run backup or
serve. When you want that file on a second machine or
shared with a team, a hosted catalog keeps it
in sync — still sealed, because the server has no key to open it with.