Two files come out of this, and the distinction between them is the whole point.
.env holds real secrets. It goes in .gitignore and never gets committed.
.env.example holds the same keys with empty values. It gets committed, documents what the application needs, and lets a new developer see immediately which variables are missing.
Every secret is freshly generated
Session secrets, API keys, JWT signing keys, encryption keys and database passwords are all drawn from crypto.getRandomValues at the strength you select. None of them is a template value that someone might ship.
The database URL keeps sslmode=require. Without it, PostgreSQL will happily fall back to an unencrypted connection and your database credentials cross the network in the clear.
Naming that survives contact with reality
- Upper snake case. Every dotenv parser and shell expects it.
- Prefix by service.
PAYMENTS_API_KEYrather thanAPI_KEY— you will end up with several. - Environment in the name where it matters.
STRIPE_LIVE_SECRET_KEYis harder to paste into the wrong file thanSTRIPE_KEY. - Never prefix a secret with
NEXT_PUBLIC_,VITE_,REACT_APP_orPUBLIC_. Those are inlined into your client bundle at build time. The hardcoded secret linter flags this as critical, because it is.
Quoting
Values containing spaces must be quoted or some parsers truncate at the first space. Values containing $ may be expanded by a shell if the file is sourced rather than parsed. When in doubt, quote — the option is there.
.env is a development convenience
In production, read secrets from a secrets manager at runtime rather than from a file on disk. A .env file:
- Sits in plaintext on the filesystem, readable by any process running as your user.
- Ends up in Docker images if the
COPYis careless. - Cannot be rotated without a redeploy.
- Has no audit trail — you cannot tell who read it.
The secrets manager formatter produces the equivalent payload for AWS Secrets Manager, Vault, Google Secret Manager, Azure Key Vault and Doppler.
Before you save the file
Add the ignore rules first. The gitignore generator produces them along with a pre-commit hook that blocks .env even if the ignore rule is missing.
Frequently asked questions
Should .env.example have real-looking values?
No. Empty values, or an obvious placeholder like <your-key-here>. A realistic-looking value gets copied into production by someone in a hurry, and then you have a shared secret that is in your public repository.
What if I have already committed a .env?
The secrets in it are compromised. Rotate every one — the revocation generator has provider commands. Then remove the file, add the ignore rule, and rewrite history with git filter-repo. Rotation is what protects you; history rewriting is housekeeping.
How do I handle different environments?
Separate files — .env.development, .env.production — with entirely separate secrets, and ideally entirely separate provider accounts. Never the same key in two environments.
Should the .env file be in my Docker image?
No. Inject secrets at runtime with --env-file, Docker secrets, or your orchestrator's secret mechanism. Anything baked into an image is in every layer, every registry copy and every pull.