Cloud and infrastructure as code
Config layering and validation
Config comes from layers, and the most specific one wins. Load it once at startup into a typed structure, and refuse to boot when a required value is missing or wrong.
A running service reads its settings from several places at once. The defaults come from code, the environment supplies overrides, and secrets come from a vault. Config layering is the rule for which value wins. Validation is the check that stops a bad value from running at all.
The big idea
Think of layers like a set of transparent overlays. Each higher layer covers the one below it, but only where it has a value. The most specific layer that sets a key wins. The layers, from lowest to highest, are these.
defaults in code (safe, non-secret)
-> config file for the environment class (timeouts, pool sizes)
-> environment variables / platform config
-> runtime flags / remote config
-> secrets from a vault (never in git)The analogy breaks when two layers disagree silently. A stale default can hide a missing environment variable, so the app runs with the wrong value and no error. Validation closes that gap.
Walking through a typed loader
Load config once, at startup, into a typed object. If a required value is missing or out of range, throw.
type Config = { port: number; databaseUrl: string; redisUrl: string; outboundTimeoutMs: number };
function loadConfig(env: NodeJS.ProcessEnv): Config {
const port = Number(env.PORT ?? "8080");
const databaseUrl = env.DATABASE_URL ?? "";
const redisUrl = env.REDIS_URL ?? "";
const outboundTimeoutMs = Number(env.OUTBOUND_TIMEOUT_MS ?? "3000");
const errors: string[] = [];
if (!Number.isInteger(port) || port < 1) errors.push("PORT");
if (!databaseUrl.startsWith("postgres")) errors.push("DATABASE_URL");
if (!redisUrl.startsWith("redis")) errors.push("REDIS_URL");
if (!(outboundTimeoutMs >= 100)) errors.push("OUTBOUND_TIMEOUT_MS");
if (errors.length) throw new Error(`Invalid config: ${errors.join(", ")}`);
return { port, databaseUrl, redisUrl, outboundTimeoutMs };
}
try {
loadConfig({ REDIS_URL: "redis://cache:6379" });
} catch (e) {
console.log((e as Error).message);
}Output:
Invalid config: DATABASE_URLFailing at startup is kinder than serving 500s for twenty minutes. The pod restarts, and the deploy system sees failing health checks. It then marks the revision unhealthy and can roll back.
Tip
Log the non-secret config at boot, such as the region and pool sizes. Never log secret values. A short hash of the non-secret settings lets you confirm that two instances run the same release.
Secrets belong in a manager such as AWS Secrets Manager, GCP Secret Manager or Vault. They are injected as environment variables or files at runtime. The secrets and encryption page covers that in detail.
Common mistakes
- Reading config lazily on the first request. The bad value then appears in production traffic, not in the deploy.
- Silent defaults for required values. An empty
DATABASE_URLshould stop the boot, not fall back to something. - Logging secret values at boot. Log the names and a fingerprint, never the values.
Check yourself
- Why fail at startup rather than on the first request? Answer: A bad config then fails the deploy's health checks instead of returning errors to users for minutes.
- A default in code and an environment variable both set
PORT. Which one wins? Answer: The environment variable, because it sits in a more specific layer.
Key takeaways
- Config comes from layers, and the most specific layer that sets a key wins.
- Load config once into a typed structure, and validate every required value.
- Fail at startup, so the deploy system can see and stop a bad release.
- Keep secrets in a vault, and never log their values.
Further learning
Next, see how a flag can turn a feature on without a deploy: feature flags.
Related
Configuration, environments and feature flags
Twelve-factor config, environment parity, secrets injection, feature flags and the blast radius of a bad config push.
Secrets management and encryption
Keeping credentials out of code and data safe at rest and in transit with KMS, Vault and envelope encryption — plus how to catch leaks before they become incidents.
Deployment strategies and safe releases
Rolling, blue-green and canary deploys, automated rollback, and zero-downtime schema changes - ship often without betting the fleet on every commit.