One .env file is fine for one machine. Real projects run in several places: a developer laptop, a test server, staging and production. Each needs different settings, and frameworks have conventions for handling that. The conventions vary, so know your tool's rules.
The environments
- Development: your machine, with local databases and debug settings.
- Test: automated tests, ideally isolated from real services.
- Staging: a production-like environment for final checks.
- Production: the live system, with real credentials and strict settings.
The goal is that the same code runs everywhere and only the configuration differs.
Common file names
| File | Typical purpose | Committed? |
|---|---|---|
.env |
Defaults shared by all environments | Sometimes, if it holds no secrets |
.env.local |
Your machine's overrides and secrets | No |
.env.development |
Settings for development mode | Often, without secrets |
.env.production |
Settings for production builds | Often, without secrets |
.env.test |
Settings for tests | Often |
.env.example |
Template listing required keys | Yes |
These names come from conventions popularised by frameworks, not from a standard. Whether a given file is loaded at all depends on the tool.
Example: Next.js and Vite style precedence
Frameworks such as Next.js load several files and apply a priority order. As a general pattern in these tools, real environment variables win first, then mode-specific local files such as .env.development.local, then .env.local (not loaded in test mode), then mode-specific files such as .env.development, then .env. Vite follows a very similar scheme with .env, .env.local, .env.[mode] and .env.[mode].local. Check the documentation for the exact order in your version.
Some frameworks also restrict which variables reach the browser. A prefix such as NEXT_PUBLIC_ or VITE_ marks variables that are bundled into client-side code, where anyone can read them. Never put a secret behind such a prefix.
Example: plain dotenv libraries
The basic dotenv library for Node.js loads one file, .env by default, and does not overwrite variables already set in the environment. You choose a different file by passing a path, so the environment-specific logic is yours to write. Python's python-dotenv works similarly, with an option to override existing variables.
Real environment variables come first
In the standard libraries, a variable already set in the process environment is not overwritten by the file. This is deliberate: on a production server, the platform's environment settings should win over any stray file. It also explains a common confusion: you edit .env, nothing changes, and the cause is a variable already exported in your shell.
Production: skip the file if you can
On hosted platforms, set variables through the platform's settings or secrets manager rather than shipping a .env file. Files are easy to leak, are copied around, and tend to drift. Containers should receive configuration at runtime, not have a file baked into the image.
A sensible layout
- Commit
.env.examplelisting every key. - Commit non-secret defaults in
.envor.env.developmentif your framework uses them. - Keep secrets in
.env.local(ignored by git) on developer machines. - Use the platform's secret store for staging and production.
- Document which variables are required, with allowed values.
Avoiding drift
- Add a startup check that fails fast when a required variable is missing, instead of failing later in an obscure way.
- Keep
.env.examplecurrent in the same commit that adds a new variable. - Use a distinct credential per environment, so a leak in development does not expose production.
Checking the files
Syntax slips such as unclosed quotes or lines without = are easy to make across several files. Docento's Text & Markdown Editor checks .env and .env.* files in the browser and shows the failing line. For syntax rules see env file syntax, and for secrets handling see keeping .env files out of git.
Takeaway
Use a committed .env.example, framework-specific files for non-secret defaults, ignored local files for your secrets and the platform's secret store in production. Learn your framework's precedence rules, and remember real environment variables normally win.