Penv Cloud
Start free
Docs
Conceptsince 1.0.0-alpha.3

Keep .env out of git

Commit .env.schema instead of .env, let penv check and penv scan catch a tracked secret, and recover when a .env is already in your git history.

PORT=3000                      # a setting: fine to commit
STRIPE_SECRET_KEY=sk_live_...  # a secret: never

A secret you commit once stays in the history, every clone, every fork, and every CI cache that checked the repository out. A private repository is no exception: visibility is a permission, and permissions change.

Commit the schema

penv init

penv init writes .env.schema from .env and adds .env to .gitignore. The schema holds key names, types and required flags, and no secret:

.env.schema
# @type=port @sensitive=false
PORT=3000

# @type=string
STRIPE_SECRET_KEY=

penv copies a value only when it looks non-secret (such as 3000 or us-east-1) or its key has a bundler public prefix, and never when the key name marks a credential. From a dotenv file lists the rules.

Catch a tracked secret

penv check
penv scan --install-hook
CommandFails (exit 3) when
penv checkA value file holding a secret is tracked (fix: git rm --cached, commit, rotate) or not in .gitignore (fix: penv init)
penv scan --stagedA staged file holds one of your sensitive values; --install-hook runs it as a pre-commit hook

Where values live

SetupValues
Solo projectYour local .env, gitignored
TeamPenv Cloud, via penv push, which deletes your local file once the write succeeds; each teammate resolves values at run time

See .env file vs a secrets manager.

Recover a committed .env

Treat every value in it as leaked.

Rotate every credential

Rotate at each issuing provider. Rewriting history does not un-send a pushed key. See a credential leaked.

Rewrite history

git filter-repo --path .env --invert-paths
git push --force

Use git filter-repo or the hosting provider's tooling. Everyone with a clone re-clones.

Prevent a repeat

Add .env to .gitignore and commit .env.schema.

.env.example

.env.schema replaces .env.example: the same non-secret defaults plus types, checked on every run. Keep .env.example only if other tooling reads it, and keep real values out of it.