# Should you commit .env files? What belongs in Git

> Commit the template, never the values. Which .env files go in Git, what frameworks say, encrypted .env files, and where the real values should live.

URL: https://crunu.com/blog/should-you-commit-env-files
Language: en
Updated: 2026-10-30

No. A `.env` file with real values, such as API keys, database passwords or signing secrets, should not be committed, even to a private repository. What belongs in Git is a template: a `.env.example` with every variable name, a comment on what it is for, and a placeholder or a harmless default. The one real exception is a file of defaults that contain no secrets, which some frameworks expect you to commit. Encrypted `.env` files are a third option, and they move the problem to wherever the decryption key lives.

This post sorts the common files into "commit" and "don't", explains what changes once a value is in Git history, and shows a setup where nobody needs a local `.env` with production values at all. The wider question of how a team should share these files is covered in the guide on [sharing .env files with a team](https://crunu.com/guides/share-env-files-with-a-team).

## The short answer, file by file

| File                                 | Commit it?  | Why                                                          |
| ------------------------------------ | ----------- | ------------------------------------------------------------ |
| `.env.example`                       | Yes         | Names and placeholders only; it documents what the app needs |
| `.env` with real values              | No          | Every clone and every copy of history gets the secrets       |
| `.env.local`, `.env.*.local`         | No          | Meant for each developer's own overrides and secrets         |
| `.env.test` with non-secret defaults | Usually yes | Tests should behave the same for everyone                    |
| `.env.production` with real values   | No          | Production secrets don't belong anywhere a laptop can clone  |
| Encrypted `.env` (dotenvx, SOPS)     | Possible    | Safe only as long as the key stays out of the repository     |

The rule underneath the table: Git should hold the shape of your configuration, not its secrets.

## Why a private repository isn't enough

"It's a private repo" is a common argument for committing a `.env`. Private limits who can clone, but cloning is exactly the problem.

*Figure: Diagram of where a secret goes once it is in a Git repository. Git host: the repository, pull requests and forks. Commit history: old commits keep the value after the file is deleted; it stays after you delete it. CI caches and artifacts: copies of the checked-out files. Every clone: laptops and build machines that pulled it; it stays after you delete it. Everyone with read access: today and anyone added later. Deleting the copy doesn’t undo the leak, so the value has to be reissued at its provider.*

Once a value is committed and pushed, it is in every clone made since, on every developer laptop, in CI caches and build runners, in backups of the hosting service, and in forks. Deleting the file in the next commit removes it from the working tree, not from history: anyone who checks out an older commit gets it back.

Access also drifts. The set of people who can read a repository grows with every new hire, contractor and integration, and it rarely matches the set of people who should hold the production database password. When someone leaves, you can remove them from the repository, but you can't remove the copy they cloned. That turns every secret in the history into something to reissue.

## What the frameworks say

The tools agree on the main point and differ on the details.

- **Next.js.** The documentation warns that "the default `create-next-app` template ensures all `.env` files are added to your `.gitignore`. You almost never want to commit these files to your repository." For tests it makes an exception: "`.env.test` file should be included in your repository, but `.env.test.local` shouldn't, as `.env*.local` are intended to be ignored through `.gitignore`" ([Next.js: environment variables](https://nextjs.org/docs/app/guides/environment-variables)).
- **Vite.** "`.env.*.local` files are local-only and can contain sensitive variables. You should add `*.local` to your `.gitignore` to avoid them being checked into git" ([Vite: env variables and modes](https://vite.dev/guide/env-and-mode)).
- **dotenv.** The README of the Node.js `dotenv` package answers "Should I commit my .env file?" with "No." and adds that the exception is a file encrypted with dotenvx ([dotenv on GitHub](https://github.com/motdotla/dotenv)).
- **GitHub's `.gitignore` template for Node** ignores `.env` and `.env.*`, and explicitly keeps `!.env.example` ([github/gitignore](https://github.com/github/gitignore/blob/main/Node.gitignore)).

The per-framework rules for which files load in which order are on each stack's page, for example [environment variables in Next.js](https://crunu.com/environment-variables/nextjs).

## What counts as a secret

The line between "configuration" and "secret" decides which file a value goes in, so it helps to draw it on purpose.

- **Secret:** anything that grants access or proves identity. API keys, database URLs with passwords in them, OAuth client secrets, webhook signing secrets, session and JWT signing keys, private keys.
- **Not secret:** values that would do no harm on a public web page. Ports, log levels, feature flags, the name of a region, a public site URL.
- **Public by design, still not for Git:** keys that providers mark as safe to expose in a browser, such as a publishable payments key. They aren't secrets, but they differ per environment, so they belong with the other per-environment values rather than hard-coded in history.

When in doubt, treat a value as secret. Moving a value out of the secret list later costs nothing; reissuing one you committed takes real work.

## What a good `.env.example` contains

The template is the part you do commit, so it is worth doing well. It should let a new teammate see every variable the app reads, know where to get each value, and start the app with safe defaults where safe defaults exist.

```env
# Copy to .env.local and fill in. Never commit real values.

# Postgres connection for local development
DATABASE_URL=postgres://localhost:5432/payments_dev

# Stripe test-mode secret key, from the Stripe dashboard (Developers → API keys)
STRIPE_SECRET_KEY=

# Signing secret for the local webhook listener
STRIPE_WEBHOOK_SECRET=

# Safe to share: not a secret
LOG_LEVEL=info
```

A few habits keep it useful:

- **Empty or obviously fake values for secrets.** A placeholder that looks real tends to end up in production.
- **A comment per variable.** Where the value comes from matters more than what it is called.
- **Same names as the real files.** A template that drifts from what the app reads is worse than none. A linter that compares the two catches drift early; the [env linter](https://crunu.com/tools/env-linter) does this in the browser.

The glossary entries for the [.env file](https://crunu.com/learn/dotenv-file) and the [.env.example file](https://crunu.com/learn/env-example-file) go into the format itself.

## A `.gitignore` that holds up

Ignore every env file by default and allow the template back in:

```text
.env
.env.*
!.env.example
```

Two things catch people out. First, `.gitignore` only affects untracked files. Git's documentation says so in its first lines: "Files already tracked by Git are not affected." If a `.env` was committed before the rule existed, it stays tracked; `git rm --cached .env` stops tracking it, and the rule keeps it out afterwards ([Git: gitignore](https://git-scm.com/docs/gitignore)). The values in earlier commits are still exposed, which is the subject of the next section.

Second, check the rule rather than trusting it:

```sh
git check-ignore -v .env .env.local .env.production
```

Each file should print the line of `.gitignore` that matches it. A file that prints nothing is not ignored.

## Encrypted `.env` files: what you gain, where the key goes

A third approach is to commit the file but encrypt the values. dotenvx encrypts each value in place and tells you to "commit your encrypted .env files with your code" ([dotenvx quickstart](https://dotenvx.com/docs/quickstart)). The `dotenv` README describes the production flow: encrypt `.env.production`, put the private key from `.env.keys` on the server, for example as `DOTENV_PRIVATE_KEY_PRODUCTION`, and commit the encrypted file. SOPS works on the same idea with age, PGP or cloud KMS keys.

What you gain is real. Configuration is versioned next to the code that uses it, changes go through review, and a stolen copy of the repository is useless without the key.

What you take on is also real:

- **The key is the secret now.** Every developer and every machine that needs the values needs the private key, and it has to travel to them some other way.
- **One key opens a whole file.** Anyone with the production key reads every production value in it.
- **Removal is hard.** When someone with the key leaves, the old key still opens every earlier commit. The post on [removing someone from git-crypt, SOPS or Ansible Vault](https://crunu.com/blog/remove-someone-from-git-crypt-sops-ansible-vault) walks through why.

For a small team that already lives in Git, that trade can be worth it. Just count the key as a secret with its own sharing and offboarding plan.

## If you already committed one

Assume every value in it is exposed from the moment it was pushed, and reissue those values at their providers before you clean up history. Rewriting history first feels productive but leaves the old values working. The order, the tools and what scanners catch are in [a .env file leaked into Git](https://crunu.com/blog/env-file-leaked-in-git).

## Keeping real values out of the working directory

The cleanest answer to "should I commit my `.env`?" is to make the question rarely come up: keep the template in Git and the values somewhere that hands them to the app when it starts.

*Figure: Priya Natarajan dragged the .aws folder from her computer onto the payments-api production environment. Crunu recognized two files and proposes where each one goes: \~/.aws/config, marked as able to run commands, and \~/.aws/credentials. Only field names such as aws_access_key_id are listed; values are not shown. The cursor rests on Add (2).*

In Crunu, you drag the existing `.env` files, or whole folders, onto an environment such as `web-app / development`. Crunu shows the variable names it found, without values, and the path where each file belongs, then stores them encrypted on your device; the server holds the ciphertext and metadata such as names, not the values. Each teammate gets only the environments they have a role on, so a developer can read development without holding the production key.

To run the app, a developer signs in once and starts it through the agent, which passes the variables in the process environment instead of writing a file:

```sh
crunu login
crunu unlock
crunu run --env web-app/development -- npm run dev
```

`crunu run` loads the files marked **Load as environment variables (crunu run)**. If a tool insists on a file, `crunu pull` writes the environment's files to their paths with permissions 0600. Either way, the program and anything it starts can read the values, so this keeps secrets out of Git and out of chat, not out of the app.

The `.env.example` stays in the repository. It is still the best documentation of what the app needs.

## Sources

- [How to use environment variables in Next.js, Next.js documentation](https://nextjs.org/docs/app/guides/environment-variables), checked October 30, 2026.
- [Env variables and modes, Vite documentation](https://vite.dev/guide/env-and-mode), checked October 30, 2026.
- [dotenv README](https://github.com/motdotla/dotenv), checked October 30, 2026.
- [Node.gitignore, github/gitignore](https://github.com/github/gitignore/blob/main/Node.gitignore), checked October 30, 2026.
- [gitignore, Git documentation](https://git-scm.com/docs/gitignore), checked October 30, 2026.
- [dotenvx quickstart](https://dotenvx.com/docs/quickstart), checked October 30, 2026.
