12-factor config precedence in Python
The 12-factor app says “store config in the environment,” but a real service also has .env files, config files, and defaults. Reconciling them with the 12-factor ideal means putting the environment on top of a deterministic ladder. This page does that, extending Configuration Precedence Rules.
Problem 1: config baked into code
# ANTI-PATTERN: config that differs per environment lives in code
if socket.gethostname().startswith("prod"): # config branching on hostname
DATABASE_URL = "postgres://prod-db/app"
This violates factor III — config that varies between deploys must live in the environment, not in if branches.
Problem 2: the environment not actually winning
# ANTI-PATTERN: file config overrides the environment
DATABASE_URL = file_config.get("database_url") or os.environ["DATABASE_URL"]
Here the checked-in file outranks the environment — the opposite of the 12-factor rule.
Secure implementation
# config/twelve_factor.py
import os
from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings):
# Environment is authoritative; .env only seeds local gaps; defaults are last.
model_config = SettingsConfigDict(env_file=".env", extra="forbid")
database_url: str # required from the environment
log_level: str = "INFO" # default is the lowest-priority fallback
settings = Settings()
# pydantic-settings already ranks: init > OS env > .env > defaults — exactly 12-factor.
pydantic-settings encodes the 12-factor order natively: OS environment variables outrank the .env file, which outranks defaults. No hostname branching, no file overriding the environment.
Gotchas & version-specific behaviour
- Factor III wants config that varies between deploys in the environment; truly constant values can stay as defaults.
- Secrets are config too — keep them in the environment or a secret store, never in code.
- One codebase, many deploys (factor I/X): the same image reads different environment values.
extra="forbid"enforces that every environment supplies exactly the expected keys.
Production parity checklist
- No config branches on hostname,
ENVstrings, or build flags. - OS environment variables outrank
.envand defaults. - The same image runs in every environment with different injected values.
- Secrets come from the environment or a managed store.
- Required keys validated at startup.
Conclusion
The 12-factor ideal and a precedence ladder agree: the environment wins, files seed gaps, defaults are last, and nothing varies in code. For the full source-order model, see Configuration Precedence Rules.