dbt Best Practices cheat sheet
Project structure, model design, testing, CI, and performance practices for dbt projects that stay maintainable.
Structure
models/staging, models/intermediate, models/marts- Three layers with clear roles. staging is one-to-one with sources, marts are consumer-facing, intermediate is private.
One staging model per source table, no joins- Rename, cast, and clean only. Joining in staging creates the dependency tangle that makes refactors impossible.
stg_, int_, fct_, dim_ prefixes- Lineage becomes readable in a list. Consumers can guess a model's shape from its name.
Folder per source in staging, per domain in marts- staging/stripe and marts/finance. Ownership is obvious when something breaks at 2 a.m.
Never reference a source outside staging- Enforce it with dbt-project-evaluator. One direct source ref in a mart defeats the whole layering.
Model design
Declare the grain in the model description- One sentence: one row per order line per day. The cheapest bug prevention in the project.
CTEs named for what they contain, in one direction- import CTEs first, then logic, then a final select. Readable top to bottom without scrolling back.
materialized: view for staging, table for marts- Views cost nothing to build and stay fresh; marts get materialized because they are queried repeatedly.
Incremental only when a full rebuild actually hurts- Incremental adds late-arriving-data complexity. Earn it with a slow build time, do not adopt it preemptively.
Always define a lookback window on incremental models- where ordered_at > (select max(ordered_at) from {{ this }}) - interval '3 days'. Without it, late rows never load.
Testing
unique and not_null on every primary key- The non-negotiable baseline. If a model has no testable key, its grain is probably undefined.
relationships on important foreign keys- Catches orphans that silently vanish from inner joins and quietly change reported totals.
Unit tests for CASE and window logic- Fast, need no warehouse data, and catch the logic bugs that data tests structurally cannot.
severity: warn for known-imperfect sources- Keeps the build meaningful. A permanently red build trains everyone to ignore failures.
store_failures on tests you actually triage- Failing rows in a table beat rerunning with guesswork during an incident.
CI/CD
dbt build -s state:modified+ --defer --state prod-artifacts/- Slim CI. Builds only what changed and defers unchanged refs to production, turning hours into minutes.
Build into a PR-specific schema- dbt_pr_1234 so concurrent PRs never collide, with automated cleanup after merge.
sqlfluff and dbt parse in a pre-commit hook- Formatting arguments and compile errors resolved before review, not during it.
Run dbt-project-evaluator on a schedule- Flags structural drift — direct source refs, models with no tests, rejoining upstream models.
Deploy with dbt build, never dbt run alone- build runs models, tests, snapshots, and seeds in DAG order, so a failing test stops downstream models.
Performance and cost
Check the warehouse bill per model- Snowflake QUERY_TAG or BigQuery labels attribute cost per model. Usually one model dominates the run.
dbt run --threads 8- More threads shorten wall clock at higher peak warehouse load. Tune it against your warehouse size, not to the maximum.
Do not materialize what nobody queries- Audit exposures and query history. Abandoned marts are pure recurring cost.
+tags: nightly for selective scheduling- Not everything needs to run hourly. Tag by freshness requirement and schedule accordingly.
Documentation and ownership
Describe every mart model and its columns- Staging descriptions can be terse; consumer-facing models cannot. The docs site is only useful if marts are covered.
meta: owner on every model- Renders in the docs and answers who to contact without asking in Slack.
exposures for dashboards and reverse-ETL syncs- Makes the blast radius of a change visible before you merge it.
contract: enforced on published models- Fails the build when a column or type changes, turning a silent breaking change into a CI failure.
From DataLane — tutorials at/blog, practice SQL live in theplayground.