Skip to main content
Modern applications run in multiple environments. The same codebase needs different database connections, logging levels, and API keys in development, staging, and production. This page covers patterns for loading the right config in each environment, validating it, passing it through Docker and CI/CD, and safely rotating secrets.

Environment Lifecycle

1

Development

Your local machine. Local database, verbose logging, hot reload. Config: .env.development
2

Staging

Pre-production server. Production-like setup, staging database, sandbox API keys. Config: .env.staging
3

Production

Live server with real users. Production database, real API keys, minimal logging. Config: .env.production
Environment parity: keep environments as similar as possible. Differences should be config values only, not architecture or code behavior.

Pattern 1: Per-Environment .env Files

Load by NODE_ENV:
Loads files in cascading order, with later files overriding earlier ones:
Load order:
  1. .env (shared defaults)
  2. .env.local (local overrides, not committed)
  3. .env.development (or staging/production/test)
  4. .env.development.local

cross-env: Cross-Platform NODE_ENV

Setting NODE_ENV in npm scripts works differently on Windows vs Unix: cross-env normalizes this:

Centralized Config Module

Never scatter process.env throughout your codebase. Create one file that reads, validates, and exports all variables:
Never write process.env.JWT_SECRET || 'fallback-secret'. If the env var is unset in production, the app silently falls back to a known/weak default. Validate and fail loudly instead.

Environment Variables in Docker

Dockerfile (default values)

docker run with —env-file

docker-compose.yml

GitHub Actions Secrets

Store sensitive values in GitHub: Settings > Secrets and variables > Actions > New repository secret.

Secret Rotation: Zero-Downtime Process

1

Generate new secret

2

Update in secret store

Add the new secret to GitHub Actions secrets, Heroku Config Vars, or your secret manager.
3

Redeploy the application

Restart the app so it picks up the new value.
4

Verify new secret works

Run health checks. Test authenticated endpoints.
5

Revoke the old secret

Only disable the old credential AFTER confirming the new one is active.

Monitoring for Leaked Secrets

git-secrets (Pre-commit hook)

GitHub Secret Scanning

Enable in repository settings. GitHub automatically scans every commit and pull request for known secret patterns (AWS keys, GitHub tokens, etc.).

Emergency: Secret Found in Git History

If you discover a secret was committed:
  1. Rotate the secret immediately (generate new, update everywhere)
  2. Use git filter-repo or BFG Repo Cleaner to remove from history
  3. Force-push the cleaned history
  4. Notify all team members to re-clone the repo
Simply deleting the file and committing “remove secrets” does NOT remove the secret from git history. Anyone can still run git log -p to see it. Always rotate first, then clean history.

Key Terms

Common Mistakes

When process.env.X appears in 50 files, refactoring and testing become painful. Use a config module: one place to change, mock, and validate.
Every .env.* file with real secrets must be in .gitignore. Only .env.example is safe to commit.
Development and production must have separate credentials. If a developer’s laptop is compromised, you don’t want production credentials exposed.
If DB_URI is undefined, your app might start successfully and crash only when the first database query runs. Validate all required vars before the server starts.