CI/CD Config Validation
A configuration error should fail a pipeline, not a production pod. The other pages in this section repeatedly lean on “gate it in CI” — this is the dedicated page for that gate. It instantiates the settings model in the pipeline so a missing key, a malformed URL, or a committed secret stops the build. It sits within the core configuration patterns as the enforcement layer over everything else.
Secure implementation
# ci/validate_config.py — run as a standalone CI step
import sys
from pydantic import ValidationError
from config.settings import Settings # your one BaseSettings model
def main() -> int:
try:
settings = Settings() # extra="forbid" catches typo'd vars
except ValidationError as exc:
print("Config validation FAILED:", file=sys.stderr)
print(exc, file=sys.stderr) # field-level errors, no secret values
return 1
print("Config OK:", ", ".join(settings.model_dump(exclude={"api_key"}).keys()))
return 0
if __name__ == "__main__":
raise SystemExit(main())
# .github/workflows/config.yml
jobs:
validate-config:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with: { python-version: "3.12" }
- run: pip install -r requirements.txt
- run: gitleaks detect --no-banner # block committed secrets
- run: python -m ci.validate_config # fail build on bad config
env:
DATABASE_URL: $
API_KEY: $
The job constructs the model with extra="forbid", so a stray or misspelled variable fails the build. Secrets come from masked GitHub secrets and are never printed.
Configuration reference
| Step | Tool | Fails build when | Security implication |
|---|---|---|---|
| Secret scan | gitleaks |
A credential is committed | Stops leaks reaching history |
| Config validate | Settings() |
Missing/malformed key | Catches drift pre-deploy |
extra="forbid" |
pydantic | Unknown variable present | Surfaces typos in review |
| Masked vars | CI platform | — | Keeps secrets out of logs |
PYTHONWARNINGS=error |
Python | Any warning | Catches deprecations early |
Step-by-step deployment parity
- Local dev — run
python -m ci.validate_configand the pre-commit secret scanner before pushing. - CI — the pipeline runs the same validation plus
gitleaks; a failure blocks merge. - Staging — validate against staging’s injected variables before promotion.
- Production — a pre-deploy job validates against production variables; deployment is blocked on failure.
Security boundaries & operational guardrails
- Secret scanning runs in both pre-commit and CI; CI is the gate that cannot be skipped.
- Validation uses
extra="forbid"so unknown keys fail instead of slipping through. - CI variables are masked; validation output reports key names only, never values.
- Secrets are wrapped in
SecretStrso an accidental print is masked. - The validation step runs with
PYTHONWARNINGS=errorto catch deprecations.
Troubleshooting
- Build passes but production fails — CI validated against the wrong environment’s variables; validate against the target environment.
- Secret leaked despite pre-commit — someone ran
git commit --no-verify; the CIgitleaksjob is the backstop. See Pre-commit Hook to Block Committed Secrets. extra not permittedin CI only — a variable is set in CI but not in the model; add or remove it deliberately.- Secret printed in CI logs — the field is a plain
str; switch toSecretStr.
Frequently asked questions
How do I validate configuration in a CI pipeline?
Add a stage that imports and instantiates your pydantic-settings model against the target environment’s variables. If construction raises a ValidationError, the stage fails and the build stops before the misconfiguration reaches production.
Should secret scanning run in CI or in pre-commit?
Both. A pre-commit hook blocks the obvious local mistake, and a CI job is the enforced gate that cannot be bypassed with --no-verify. Run gitleaks or detect-secrets in both places.
How do I keep real secrets out of CI logs?
Use the platform’s masked variables, never echo configuration, and run validation with output that reports key names only. Wrap secret fields in SecretStr so an accidental print is masked.
Conclusion
The invariant: no configuration reaches an environment it has not passed validation for, and no secret reaches history without tripping a scanner. The pipeline, not production, is where misconfiguration dies.