What it is
Twelve-factor app is a set of rules for apps that run as services. Factor III, Config, says configuration lives in the environment, not in the codebase. Configuration here means anything likely to differ between deploys: database URLs, credentials, hostnames, queue names, mail endpoints, feature toggles, external API keys. It does not mean code structure, routes, or dependency lists.
A practical test is whether you could open source the code right now without leaking a password or pointing users at an internal host. If not, configuration is mixed into the code. The factor asks for strict separation. One codebase, one build artifact, many deploys. The artifact is identical in development, staging and production. Only the environment changes. That is the core idea.
Twelve-factor prefers environment variables. They are language and OS neutral. They are easy to change between deploys and hard to commit by accident. Files can work too, especially for certificates or large values, but the path to the file should come from the environment. The factor warns against config files grouped by environment, such as config/staging.yml, because they put deployment details back into the repo and encourage drift.
How it works
The lifecycle splits into build, release and run. Build compiles code and dependencies into an artifact, such as a container image or a jar. Release combines that artifact with a specific configuration set for one environment. Run starts the process. The artifact hash should be the same across environments; the release changes only by config. This is how you know staging tested the same code that production runs.
At runtime the process reads variables. In Node that is process.env.DATABASE_URL. In Python it is os.environ["DATABASE_URL"]. In Java it is System.getenv("DATABASE_URL"), and in .NET it is Environment.GetEnvironmentVariable. Kubernetes can inject values from ConfigMaps and Secrets with envFrom or valueFrom. AWS Systems Manager Parameter Store and Secrets Manager, HashiCorp Vault, and similar tools hold values outside the image.
Keep variables granular and orthogonal. One variable for the database URL, one for the queue URL, one for the log level. Do not pack everything into a single APP_CONFIG JSON string. A JSON blob is hard to audit, hard to change safely, and turns every edit into an all-or-nothing event. For structured values like TLS certificates, set a variable that points to a mounted file path, then read the file at startup.
Validate configuration when the process boots. Parse strings into typed values, check required keys, and fail with a clear message that names the missing variable but not its value. This turns a runtime surprise into a startup error. For local development, a .env file loaded by the app is fine, as long as production never depends on it and the file is ignored by Git.
Why it matters for a business
Teams that keep configuration in code end up with a different build for every environment. That means a fix tested in staging is not the same artifact that reaches production. It also means a merge can change production behavior without any deploy. Moving configuration into the environment makes the release step explicit. You can see which artifact and which config set went live, then repeat or roll back that release.
Security reviews ask where production credentials live and who can read them. A repo with a production password in a YAML file fails that question. So does a deploy script with a key pasted into it. Environment-based configuration gives you a single layer to control access, rotate values, and remove a credential without editing code. It also shrinks the blast radius when a laptop is lost or a repo is cloned by a new contractor.
Operations get simpler. Support can change a hostname or rotate a key with a restart, not a code change and a release. New engineers can run the app with their own local values. Finance sees fewer environment-specific builds. The business benefit is not the variable itself. It is the repeatable release and the audit trail around it.
Common mistakes
Committing .env files, application-prod.yml, or a Kubernetes Secret manifest with real values. Another version is baking configuration into a frontend build. In React with Vite, variables prefixed VITE_ are compiled into the JavaScript bundle. In Next.js, NEXT_PUBLIC_ variables are public. Those are build-time settings, not secrets. Keep private keys on the server and read them at request time.
Using code defaults as a safety net. A default database URL that points at localhost seems helpful until production starts and connects to the wrong place, or fails late. Make required variables required. Do not hide a missing production value behind a fallback.
Letting names drift. One service reads DB_HOST, another reads DATABASE_HOST, and a third reads POSTGRES_HOST. A runbook says one thing and the deployment sets another. Pick a naming pattern, document each variable, and keep the list in version control without the values. That list is a contract, not a secret.
Treating every setting as configuration. Route definitions, timeouts that are part of business logic, and feature behavior usually belong in code. Feature flags are a gray area: they change at runtime, but they need owners and expiry dates. Also avoid logging configuration at startup. A helpful debug line can print a password into a log aggregator.
How to get started
Inventory first. Search the repo for hardcoded hosts, keys, and environment names. List each setting, who owns it, whether it is secret, and which environments need a different value. Classify each as config, code or a feature flag. This list becomes the contract for every deploy.
Move values out. Add a startup config module that reads variables, validates them, and exposes a typed object to the rest of the app. Put real values in your platform's secret store or parameter store. Keep a sample file in the repo with keys and empty values. Run the app once with no local .env file and a production-like config set to prove it starts cleanly.
Change the release process. Build once, promote the same artifact, and record which configuration set went with each release. Give deploy roles read access only to the values they need. Rotate one secret as a drill and confirm the app restarts with the new value. If the team needs a repeatable platform, Cloudverg's Delivery platform setup at $14,500/month is one option among others.
Frequently asked questions
What does twelve-factor config mean?
It means configuration lives in the environment, outside the codebase and outside the build artifact. Values such as database URLs, credentials and hostnames change by deploy, while the code stays the same. The goal is one artifact promoted through every environment.
Should I use environment variables or config files?
Use environment variables for values that differ by deploy. Use files for large values such as certificates, and pass the file path through an environment variable. Avoid checked-in files named after environments.
Are environment variables safe for secrets?
They are common and supported everywhere, but they are not invisible. On Linux, another process with the same user can often read the environment, and debug pages or crash dumps can expose values. Keep access tight, avoid logging them, and use a secret manager for high-value credentials.