Practical guides2 min read
How to manage environment variables across development, staging, and production
A workable approach to keeping environment variables consistent and separate across development, staging, and production without copying files around.
By the Keel team
Most configuration bugs come from the same few causes: a variable that exists in one environment and not another, a production value used in development, or a change made in one place and forgotten in the rest. A little structure removes most of them.
1. Keep one list of variable names
Maintain a single canonical list of the variables your application needs. A committed .env.example file works well:
# .env.example: names only, no real values
DATABASE_URL=
SESSION_SECRET=
STRIPE_SECRET_KEY=
LOG_LEVEL=infoThis documents what an environment must provide and lets a new developer see what to fill in.
2. Give every environment its own values
Development, staging, and production should never share credentials. Use separate database users, separate third-party API keys (test mode where the provider offers it), and separate signing secrets. Isolation means a mistake in development cannot damage production.
3. Make staging resemble production
Staging only catches problems if it is configured like production. Keep the same variable names, the same feature flags, and the same shape of values. Differences should be intentional and few.
4. Store values centrally, not in files
Passing .env files by chat or email creates copies you cannot update or audit. Keep the real values in one store, import existing files once, and let people read only the environments they need. Keel's environment variable management is built around this: one project, three environments, per-environment access, and .env import and export.
5. Restrict who can read production
Most developers do not need production credentials to do their work. Grant production access to the few people and systems that deploy or operate the service, and keep a record of reads. See role-based access control.
6. Handle changes deliberately
- Change a value in one place, then deploy or restart the services that read it. Environment variables are read at process start, so running processes do not notice a change.
- Keep history so a bad value can be reverted quickly. See secret version history.
- Add new variables to staging first, confirm the application behaves, then add them to production before the code that needs them ships.
7. Validate at startup
Fail fast when configuration is missing, instead of discovering it on the first request:
const required = ["DATABASE_URL", "SESSION_SECRET"];
const missing = required.filter((k) => !process.env[k]);
if (missing.length) {
throw new Error(`Missing environment variables: ${missing.join(", ")}`);
}For the background on why configuration belongs in the environment, see the twelve-factor config principle.
Related reading
- Environment variables vs. secrets: what is the difference?Environment variables are a way to deliver configuration. Secrets are a kind of value that needs protection. Here is how the two overlap and why it matters.
- How to keep secrets out of Git repositoriesPrevent API keys and passwords from reaching Git with ignore rules, scanners, and push protection, and learn the correct cleanup order if one gets committed.
- Common secrets management mistakes in software developmentThe mistakes that most often lead to leaked credentials, from committed .env files to shared production keys, and what to do instead.
- Environment variablesSeparate Development, Staging, and Production values, with .env import and export.
- Access controlOwner, admin, member, and viewer roles, plus explicit per-environment grants.