Implementing Data CI/CD
When to use
- Adding CI checks to a dbt/SQL/pipeline repo.
- Automating lint, compile, and tests on pull requests.
- Speeding up CI by building only changed models (Slim CI).
- Promoting changes across dev → staging → prod.
- Do NOT use for reviewing an individual PR's logic (use
reviewing-data-pipeline-code).
Workflow
- [ ] Lint SQL (SQLFluff) and check formatting on every PR
- [ ] Compile the project to catch ref/Jinja errors early
- [ ] Build + test only changed models against prod parents (Slim CI)
- [ ] Run in an isolated CI schema; tear it down after
- [ ] Promote across environments with the same code, different targets
- Lint first — SQLFluff catches style and some correctness issues fast and cheap, before spinning up a warehouse.
- Compile —
dbt compile(orparse) fails on badref()/Jinja without running SQL. - Slim CI — build and test only
state:modified+using a productionmanifest.jsonas state, and defer unchanged parents to prod so CI doesn't rebuild the whole project. - Isolate — run into a unique CI schema (e.g. per PR) and drop it afterward so runs don't collide.
- Promote — the same code runs against dev/staging/prod via
--target; never fork logic per environment.
Patterns
dbt Slim CI (GitHub Actions sketch):
- run: dbt deps
- run: dbt compile
- run: |
dbt build --select state:modified+ \
--defer --state ./prod-artifacts \
--target ci
--select state:modified+ builds changed models and their children; --defer --state points unchanged parents at prod, so CI stays fast. A fresh/stale
prod-artifacts/manifest.json is required for correct selection.
SQLFluff config (.sqlfluff) — pin the dialect and templater so lint matches
your warehouse and dbt:
[sqlfluff]
dialect = snowflake
templater = dbt
Environment promotion — dev (developer schemas) → staging (full build on
merge) → prod (scheduled). Same repo, different profiles.yml targets and
credentials.
Common pitfalls
- Rebuilding the whole project on every PR — slow and expensive; use Slim CI
with
state:modified+ deferral. - Stale/missing state manifest —
state:modifiedselects the wrong nodes; refresh prod artifacts in CI. - Shared CI schema across PRs — concurrent runs clobber each other; use a unique schema per PR and drop it.
- Skipping
dbt depsin CI — macro/package-not-found failures. - Different logic per environment — drift and "works in dev" bugs; vary only the target/config, not the code.
- No teardown — orphaned CI schemas accumulate cost.