Three environments, one same code
The same code runs in several environments:
- dev (development): your machine, toy data, everything verbose.
- staging (pre-production): a faithful copy of production to test before publishing. This is where the bugs that dev doesn't see get caught.
- prod (production): what real people use. Stability and performance above all.
What changes between environments is not the code: it's the configuration (database URL, API keys, log level...).
Environment variables and 12-factor
The 12-factor methodology proposes, among other things, separating the
configuration from the code and reading it from the environment. In Node that's
process.env:
const config = {
port: process.env.PORT || 3000,
database: process.env.DATABASE_URL,
environment: process.env.NODE_ENV || "development",
};
This way the same artifact (the same build) runs in dev, staging and prod just by changing the variables. Never put production values hardcoded in the code.
Secret management
Keys and passwords are secrets. Golden rules:
- Never in the repository (nor in a versioned
.env). A secret in Git is a leaked secret. - In production, use a secret manager or vault (AWS Secrets Manager, Google Secret Manager, HashiCorp Vault, or your PaaS's "encrypted environment variables"). The app requests the secret at startup; it's never written to disk.
- Rotate secrets periodically and give each service only the ones it needs.
# .env stays IGNORED by Git; only the example without real values is versioned
echo ".env" >> .gitignore
git add .env.example # documents WHICH variables are needed, not their values
Feature flags
A feature flag is a switch that enables or disables a feature without deploying again. It serves to launch to a percentage of users, do A/B tests or turn off something that's failing instantly.
if (featureActive(flags, "new-checkout", user)) {
showNewCheckout();
} else {
showClassicCheckout();
}