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

  1. Local dev — run python -m ci.validate_config and the pre-commit secret scanner before pushing.
  2. CI — the pipeline runs the same validation plus gitleaks; a failure blocks merge.
  3. Staging — validate against staging’s injected variables before promotion.
  4. 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 SecretStr so an accidental print is masked.
  • The validation step runs with PYTHONWARNINGS=error to 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 CI gitleaks job is the backstop. See Pre-commit Hook to Block Committed Secrets.
  • extra not permitted in 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 to SecretStr.

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.