BLOG/CONFIGURATION MANAGEMENT
THEME
CONFIGURATION MANAGEMENT

Validate config in CI before prod

Catch the broken config in the pull request, not in the 2am page. Lint the structure, validate references with --strict, and assert the values are sane — three commands that turn config mistakes into a red CI check.

May 7, 2026 OrchStep Team 6 minROLE: DevOps EngineerSCALE: Any
RUNNABLE DEMO
Full source for this post: blog/validate-config-ci
VIEW SOURCE

The worst config bugs aren't the loud ones. They're the typo in a replica count, the environment name nobody defined, the variable referenced but never set — the kind that lints fine, deploys fine, and then quietly does the wrong thing in production at 2am.

The fix is boring and it works: make the config prove itself in the pull request. Three commands, three failure modes caught, all before merge.

Three checks, three failure modes

Each command catches a different class of mistake. lint catches shape and syntax. validate --strict catches references that won't resolve. A check task asserts the actual values are within bounds.

orchstep.yml
name: config-check
defaults:
  stage: staging
  image: ghcr.io/acme/api:1.6.0
  replicas: "3"

tasks:
  # orchstep run check
  check:
    desc: "Assert required config is present and sane"
    steps:
      - name: image_set
        func: assert
        args:
          condition: '{{ ne vars.image "" }}'
          message: "image must be set"

      - name: stage_known
        func: assert
        args:
          condition: '{{ or (eq vars.stage "staging") (eq vars.stage "production") }}'
          message: "stage must be staging or production"

      - name: replicas_set
        func: assert
        args:
          condition: '{{ ne vars.replicas "" }}'
          message: "replicas must be set"

      - name: ok
        func: shell
        do: echo "config valid for {{ vars.stage }} -> {{ vars.image }} x{{ vars.replicas }}"

  # the task CI validates structurally with: orchstep validate deploy --strict
  deploy:
    steps:
      - name: deploy
        func: shell
        do: echo "deploying {{ vars.image }} to {{ vars.stage }} ({{ vars.replicas }} replicas)"

The assert steps fail the run with a clear message when a value is wrong. stage_known is the interesting one: pass --var stage=qa and the gate fails because qa isn't a stage you defined — exactly the typo that would otherwise sail through.

Wire it into the PR

The CI step is three lines. Each exits non-zero on failure, so a broken config turns the check red:

orchstep lint
orchstep validate deploy --strict
orchstep run check

lint checks the workflow's structure. validate deploy --strict statically resolves the deploy task's references and fails on anything left unresolved — a variable you referenced but never defined, for instance. run check then asserts the values themselves are in range.

Prove the gate actually fails

A gate you've never seen fail is a gate you don't trust. Feed it a bad value on purpose:

GOOD CONFIG
$ orchstep run check
config valid for staging -> ghcr.io/acme/api:1.6.0 x3
Result: success
BAD STAGE
$ orchstep run check --var stage=qa
[FAIL] stage_known
  stage must be staging or production
Result: failed

The bad value exits non-zero, the PR check goes red, and the typo never reaches a cluster. That's the whole point: the failure happens in CI, where it costs a re-push, not in prod, where it costs an incident.

What you gained

Failure modeWithout a gateWith the gate
Malformed YAMLdeploy-time errororchstep lint
Unresolved referencewrong value at runtimevalidate --strict
Out-of-range valuequiet prod bugassert in check
Unknown environment namesilent fallthroughred PR check
When you find out2am pagecode review

Where to stop

Don't try to assert every conceivable property — a config check that takes longer to maintain than the config it guards is a net loss. Pin the handful of values that have actually burned you: the environment names, the must-be-set secrets references, the counts that must be positive. That's where the leverage is.

Where to go next

The project above is a runnable, echo-only demo. Run orchstep run check --var stage=qa to watch the gate fail on purpose.

#CI#VALIDATION#CONFIGURATION#PR-GATE
Try it in two minutes — one binary, no signup.
curl -fsSL https://orchstep.dev/install.sh | sh

RELATED — CONFIGURATION MANAGEMENT