Docs
Conceptsince 1.0.0-alpha.3

Should a .env file be in git?

No, and the reason is git's memory, not the file. What to commit instead, and how to recover when a .env is already in the history.

A .env file holds two different kinds of line, and only one of them belongs in git.

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

Git never forgets. A secret committed once and deleted the next day is still in the history, in every clone, in every fork, and in every CI cache that ever checked the repository out. Deleting the file removes it from the working tree, not from the past. That is why the answer is no even for a private repository: private is a permission, and permissions change.

Commit the shape, not the values

What the repository needs is the list of keys the app expects, their types, and which ones are required. That is a schema, and it is safe to commit because it carries no secret.

.env.schema
# @schema=1

# @type=port @sensitive=false
PORT=3000

# @type=string
STRIPE_SECRET_KEY=

penv init writes this file from the .env you already have and adds .env to .gitignore in the same step. A value is copied across only when it is dull enough to be certain about, such as 3000 or us-east-1, and never when the key name says it holds a secret. What init reads lists every rule.

With the schema committed, a teammate who clones the repository knows exactly what to provide, and penv check tells them what is missing before the app starts.

Where the values go instead

Somewhere that is not a file in the repository. For a solo project, a local .env that git ignores is enough. For a team, the values go to Penv Cloud with penv push, which deletes the local file once the write succeeds, and each person fetches their own copy at run time. The difference between those two is the whole of .env file vs a secrets manager.

A .env is already in the history

Treat every value in it as leaked, because it is. The order of work is:

  1. Rotate each credential at the provider that issued it. Rewriting history does not un-send a key that was pushed.
  2. Only then remove the file from the history, with git filter-repo or the provider's own tooling, and force-push. Every clone has to be re-cloned.
  3. Add .env to .gitignore and commit .env.schema, so the next push has nothing to leak.

A credential leaked is the runbook for the first step.

The one exception

A file of non-secret defaults, often named .env.example, is safe to commit and many projects do. The schema replaces it: it holds the same defaults, adds the types, and is checked on every run instead of read once by a person. Keep the example file if other tooling reads it. Do not let a real value drift into it.