Schema Evolution & Versioning
During a rolling deployment, old and new pods run at the same time against the same injected environment. If the new release renames DB_DSN to DATABASE_URL, the old pods break the instant you switch the variable — unless the schema accepts both names. This page evolves a settings schema without a downtime window.
Safe evolution is what keeps the settings model stable as requirements change, the final discipline in the type-safe validation section.
Secure implementation
# config/versioned.py
import warnings
from pydantic import AliasChoices, Field, model_validator
from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings):
model_config = SettingsConfigDict(extra="forbid", populate_by_name=True)
# Accept the new name and the legacy name during the migration window.
database_url: str = Field(
validation_alias=AliasChoices("DATABASE_URL", "DB_DSN"),
)
# Deprecated: still accepted, but warns until the removal release.
legacy_timeout: int | None = None
@model_validator(mode="after")
def warn_deprecated(self) -> "Settings":
if self.legacy_timeout is not None:
warnings.warn(
"legacy_timeout is deprecated; use REQUEST_TIMEOUT (removed in v3).",
DeprecationWarning,
stacklevel=2,
)
return self
AliasChoices lets both old and new pods validate during the overlap. The deprecation warning gives operators a removal deadline without breaking anything today.
Configuration reference
| Tool | Purpose | Removal step |
|---|---|---|
AliasChoices(new, old) |
Accept both names | Drop old after cutover |
populate_by_name=True |
Allow field name and alias | Keep while aliasing |
| Optional deprecated field | Soft-remove a setting | Delete after warning window |
model_validator warning |
Signal deprecation | Promote to error, then remove |
Deployment parity: local to production
- Local dev — both names work; developers are unaffected mid-migration.
- CI — assert the model validates with the old name and the new name during the window.
- Staging — switch the injected variable to the new name; old-name pods still pass.
- Production — roll out, confirm no deprecation warnings fire, then remove the alias in the next release.
Security boundaries & guardrails
- Never rename and remove in the same release; always run a two-name overlap window.
- Keep
extra="forbid"— but remember a removed field then becomes an error, so coordinate removal with operators. - Treat default-value changes as behavioural changes and announce them.
- Version the schema in changelogs so every environment knows which names are valid.
Troubleshooting
- Old pods crash after a variable rename — the alias window was skipped; re-add
AliasChoicesand redeploy. extra not permittedafter removing a field — an environment still sets the old variable; stop setting it before removing the field. See Handling Breaking Changes in Production Config Schemas.- Deprecation warning never appears —
warningsfilters suppress it; run CI withPYTHONWARNINGS=error::DeprecationWarning. - Both names set with different values —
AliasChoicestakes the first match; document which name wins.
Frequently asked questions
How do I rename a config field without breaking running pods?
Accept both names during a migration window using AliasChoices in validation_alias, deploy, switch every environment to the new name, then remove the old alias in a later release. The two-name window means old and new pods both validate.
How can I deprecate a configuration field safely?
Keep the field optional, emit a warning in a model_validator when it is supplied, and document the removal release. Remove it only after every environment has stopped setting it.
Is changing a default value a breaking change?
It can be. A changed default silently alters behaviour for any environment that relied on the old one. Treat default changes like code changes — announce them and roll them out deliberately.
Conclusion
The invariant: no field is renamed or removed without an overlap window where both the old and new schema validate. Evolution is additive first, subtractive only after every environment has migrated.