Skip to content

Course 3 · Module 2 — CI/CD

It’s 2 a.m. and the deploy that breezed through staging just blew up on prod — a missing token, a reference that wasn’t there, a default that drifted. Nobody saw it coming because nobody looked before the change merged. The review checked the design; nothing checked that the change would actually apply. That gap between “looks right” and “deploys clean” is where the worst nights live.

Here’s how you close it: make the machine run the deploy before anyone approves the merge.

A pipeline sounds like a big thing. It isn’t. Strip the YAML away and a SchemaSmith pipeline is four quench commands — deploy the base, preview the change, deploy to staging, deploy to prod. That’s it. The CI file is just a wrapper that runs those commands on a build server instead of your laptop. So when you write a pipeline, you’re not learning a new tool — you’re scripting the same schemaquench you already run, with the right environment variables set for each step.

That last part is the whole trick. With env-var config, every SchemaSmith setting can come from an environment variable — prefix SmithySettings_, double-underscore for nesting. WhatIfONLY becomes SmithySettings_WhatIfONLY. Target:Server becomes SmithySettings_Target__Server. Which means the package never changes between environments. You build it once, and the env vars do the steering.

This is the payoff, so let’s be precise about it. The artifact you reviewed — the exact package, byte for byte — is the artifact that lands in staging, and the same one that lands in prod. No rebuild per environment. No “dev config” file and “prod config” file drifting apart. The only difference between deploying to staging and deploying to prod is two environment variables: which database to target, and whether to preview or apply.

In this module’s lab, the target database rides on a {{TargetDb}} script token. Set SmithySettings_ScriptTokens__TargetDb=ordersservice_staging and the quench converges staging; flip it to ordersservice_prod and the same package converges prod. One mold, every environment — the env var is the adjustable dye.

Now the gate. The most valuable CI pattern you can build with SchemaSmith is WhatIf-in-PR — running WhatIf on every pull request that touches schema. WhatIf does the full deploy — validation scripts, token replacement, DDL generation, dependency resolution — and then applies nothing. If it succeeds, the change is deployable. If it fails, the author sees exactly which statement, which token, which reference broke — in the PR, not at 2 a.m. in prod.

Here’s that gate as a GitHub Actions job. Spin up a throwaway database, deploy the base branch to establish current state, then WhatIf the PR branch:

name: Validate Schema Change
on:
pull_request:
paths:
- 'Schema/**'
jobs:
whatif:
runs-on: self-hosted
steps:
- uses: actions/checkout@v4
- name: Deploy base schema
env:
SmithySettings_SchemaPackagePath: ${{ github.workspace }}
SmithySettings_Target__Server: localhost
SmithySettings_Target__User: ${{ secrets.DB_USER }}
SmithySettings_Target__Password: ${{ secrets.DB_PASSWORD }}
run: |
git checkout ${{ github.event.pull_request.base.sha }}
schemaquench
- name: WhatIf PR changes
env:
SmithySettings_SchemaPackagePath: ${{ github.workspace }}
SmithySettings_Target__Server: localhost
SmithySettings_Target__User: ${{ secrets.DB_USER }}
SmithySettings_Target__Password: ${{ secrets.DB_PASSWORD }}
SmithySettings_WhatIfONLY: "true"
run: |
git checkout ${{ github.sha }}
schemaquench

Look at what’s not in there. No SDK install. No deploy script. No platform-specific anything — swap the database container and the exact same job validates SQL Server, PostgreSQL, or MySQL, because the adapter comes from the package. Credentials come from the secret store, never the file. The YAML is thin on purpose: the contract lives in schemaquench, and the pipeline just runs it.

WhatIf is green, the reviewer signs off, the PR merges. Now the deploy pipeline takes over — and it’s the same shape. Deploy to staging. Run your integration tests. Hit an approval gate — a human says “go” before production. Then promote the same artifact to prod, changing nothing but SmithySettings_ScriptTokens__TargetDb. WhatIf caught the SQL errors. Staging caught the environment quirks. The gate gave a person the last word. Same metal, hardened into every environment in turn.

Check yourself: What does running SchemaQuench in WhatIf mode on every pull request catch?

Deployment failures — bad SQL, missing tokens, broken references — before the code merges, because WhatIf runs the full deploy logic without executing any change.


Think of it this way. WhatIf is the dry strike — you swing the hammer in the air over the metal and see exactly where it’d land, without ever marking the piece. Run that dry strike on every pull request and you never bring a cold guess to a hot forge. Then the real blow falls the same way in every environment, because it’s the same hammer, the same mold, the same metal — only the dye changes from staging to prod.

Want the deeper treatment of pipeline shapes, environment promotion, and where the gates go? Read the Database DevOps guide on the SchemaSmith site, and the end-user guide’s CI/CD Integration chapter for the full pipeline examples across GitHub Actions, Jenkins, GitLab, and Azure DevOps.

Got a pipeline you’re trying to wire up — where the WhatIf gate sits, how your branch protection calls it, how promotion threads through your environments? Email me at forgebarrett@schemasmith.com — I read every one.

Next up: Course 3 · Module 3 — Rollback & recovery, where going backwards stops being a fire drill and becomes just deploying the version that worked.

Until then, may your dry strikes ring true, your gates hold the line, and the same blow land clean in every fire you take it to.

— Forge