Config & Secrets
Twelve-factor config, and keeping credentials out of your git history.
25 minDifficulty 1/5config · securityAI-writtenWritten by a model on 16 August 2026 and not yet read by a person. Checked automatically: schema, the pedagogical rules the hand-written material is held to, and every diagram parsed for real.
Before this
Why this exists
An API key gets hardcoded directly into a source file "just for now," the file gets committed to git, and even after the key is later removed from the code, it's permanently readable in the repository's history — anyone with clone access can find it with `git log -p`. Secrets that touch version control are compromised the moment they're committed, whether or not anyone actually looks.
The mental model
The twelve-factor app methodology's config principle is simple: anything that varies between environments (a database URL, an API key, a feature flag) belongs in ENVIRONMENT VARIABLES, read at runtime — never hardcoded into source code, which is identical across every environment and gets checked into version control forever.
How it works
Config that varies by environment must never be baked into code
A database connection string differs between local, staging, and production — hardcoding any ONE of these into the source means the code itself is now environment-specific, and deploying it to a different environment requires editing and recompiling code just to change a URL. Reading it from an environment variable at runtime means the exact SAME build artifact runs correctly everywhere, with only its environment variables differing.
A committed secret is compromised permanently, not just temporarily
Removing a hardcoded API key from the current version of a file does NOT remove it from git's history — every previous commit that contained it is still fully readable by anyone who can clone the repository, forever, unless the history itself is rewritten (a disruptive, rarely-done operation). The only real fix for a committed secret is treating it as PERMANENTLY compromised and rotating it — issuing a new one and revoking the old.
A .env file is a local convenience, not a secrets management solution
A `.env` file loaded by a library into environment variables is a reasonable way to manage LOCAL development config — but it must be listed in `.gitignore` to avoid the exact same commit-forever risk, and production secrets typically need a dedicated secrets manager (with access control, audit logging, and rotation support) rather than a plain text file sitting on a server's disk.
Config should fail loudly when missing, not silently default to something wrong
`process.env.DATABASE_URL ?? 'localhost'` silently falls back to a LOCAL database if the environment variable is missing in production — which could mean a production deploy quietly starts writing to (or reading from) entirely the wrong database, with no error at all. Validating required config at startup, and refusing to start the application if anything critical is missing, converts a silent, dangerous failure into an immediate, obvious one.
The mechanism
An application reads its configuration from environment variables at startup, rather than from hardcoded values in source code. Different environments (local, staging, production) supply different values for the same variable names, without requiring any change to the application code or build artifact itself. Secrets specifically are supplied this way too, but sourced from a secrets manager or a gitignored local file, never from anything committed to version control.
What people get wrong
- Deleting a committed secret from the codebase and committing that deletion removes the exposure.
- The secret remains fully present and readable in EVERY earlier commit that contained it — git history is additive by default, and a later commit removing something doesn't erase its presence in prior history, which anyone with repository access can still view. This is one of the most common, costly mistakes with accidentally committed secrets — the correct response is always ROTATING the secret (issuing a new one, revoking the old), not just removing it from the latest code.
- A .env file, once added to .gitignore, is a sufficient secrets management solution for production.
- .gitignore only prevents that file from being committed to version control — it says nothing about access control, audit logging, rotation, or how the file is protected on the actual production server's disk, all of which a dedicated secrets manager is designed to handle. Treating .gitignore as 'solving' secrets management confuses 'not in git' (necessary but not sufficient) with genuinely secure secret handling in a production environment.
- Providing a sensible-looking default value for a missing environment variable is a helpful safety measure.
- A silent default for something like a database URL can mean a production deployment silently operates against the WRONG resource with no error at all — for critical config, failing loudly (refusing to start) at the point the misconfiguration exists is far safer than limping along with a plausible-looking wrong value. A default that happens to work in development (like a localhost database) is exactly the kind of default that becomes dangerous specifically in production, where 'wrong but doesn't crash' can cause real, silent damage.
When not to use it
- A configuration value is genuinely the same across every environment and never needs to change (an application's name displayed in its UI, say).
- A plain constant in source code is fine here — the twelve-factor principle applies specifically to things that VARY by environment or are sensitive, not to every single configurable-looking value.
- A secret has already been committed to a shared repository's history.
- Immediately rotate the secret (revoke and reissue) rather than relying on removing it from the code or attempting to scrub git history, which is unreliable and disruptive if the repository has already been cloned elsewhere.
Terms
- Twelve-factor app
- — A methodology for building portable, scalable web applications, including the principle that config should be stored in environment variables, not code.
- Environment variable
- — A key-value pair set outside the application's source code, read at runtime, allowing the same code to behave correctly across different environments.
- Secrets manager
- — A dedicated service for storing sensitive credentials with access control, audit logging, and rotation support, distinct from plain config files.
- Secret rotation
- — Issuing a new credential and revoking the old one, the correct response to a secret that may have been exposed.
In an interview
A teammate accidentally committed an AWS access key to a shared repository, then immediately deleted it in the next commit. Is the key still safe to use?
- no — the key is still fully visible in the repository's commit history, in the commit where it was originally added
- deleting it in a later commit doesn't remove it from history; anyone with clone access can still find it via git log or by checking out the earlier commit
- the correct response is rotating the key immediately (revoke it in AWS, issue a new one), not just removing it from the current code
Can you recall it?
Why does removing a hardcoded secret from a file and committing that removal not actually fix the exposure?