Building Your DAG in dbt
Every time you run dbt run, dbt doesnโt just execute your models in whatever order they happen to appear. It builds a Directed Acyclic Graph โ a DAG โ from the dependencies you define with ref(), and executes that graph in the correct order. This is one of the most useful things dbt does, and understanding how it works gives you much better control over your pipelines.
What the DAG Actually Represents
A DAG in dbt is a map of every model and its relationships to every other model. Each node is a model (a .sql file), and each edge represents a dependency โ โmodel B reads from model A, so A must run before B.โ
The โdirectedโ part means dependencies flow in one direction: from upstream models to downstream ones. The โacyclicโ part means there are no circular dependencies โ model A cannot depend on model B if model B also depends on model A. dbt will throw an error if you create a cycle.
Example DAG
raw_orders raw_customers | | v vstg_orders stg_customers \ / v v orders_with_customers | v customer_lifetime_valueIn this graph, customer_lifetime_value is the most downstream model. It cannot be built until orders_with_customers is built, which in turn requires both staging models to complete first.
How dbt Detects Dependencies
dbt builds the graph by parsing your ref() calls. When you write:
-- models/marts/customer_lifetime_value.sqlSELECT c.customer_id, c.name, SUM(o.amount) AS total_spent, COUNT(o.order_id) AS order_countFROM {{ ref('orders_with_customers') }} oJOIN {{ ref('stg_customers') }} c ON o.customer_id = c.customer_idGROUP BY 1, 2dbt registers two edges: this model depends on orders_with_customers AND stg_customers. It stores this in the manifest so it knows the execution order.
You never need to write CREATE TABLE or manage schema names manually. dbt resolves the correct database reference for each ref() based on your target environment.
Running the Full DAG vs. a Subset
Running dbt run without arguments executes every model in the project in dependency order. But youโll often want to run a subset.
Run a single model:
dbt run --select customer_lifetime_valueRun a model and everything it depends on (upstream):
dbt run --select +customer_lifetime_valueRun a model and everything that depends on it (downstream):
dbt run --select customer_lifetime_value+Run everything in a specific directory:
dbt run --select marts/Run models with a specific tag:
dbt run --select tag:dailyThese selectors are composable. You can build fairly precise execution plans without modifying your code.
The dbt Build Command
Since dbt 1.0, the preferred command for CI and production runs is dbt build, not dbt run. The difference is that dbt build runs models, tests, seeds, and snapshots in a single command, in the order the DAG requires.
dbt build --select +customer_lifetime_valueThis will:
- Run any seed files the selected models depend on
- Run upstream models in dependency order
- Run tests on each model immediately after it builds
- Stop and surface an error if any test fails
In practice this gives you much faster feedback than running models and tests separately.
Visualizing the DAG
dbt Core generates a lineage graph you can view in dbt docs:
dbt docs generatedbt docs serveThis opens a browser UI where you can explore the full dependency graph, click into any model to see its SQL, description, and tests, and trace lineage in both directions.
dbt Cloud includes a live lineage view built into the IDE. Third-party tools like Atlan, Datahub, and Secoda also integrate with the dbt manifest to render lineage alongside other catalog metadata.
YAML Configuration in the DAG
Models in the DAG can be configured either in their SQL file (via a {{ config() }} block) or in a schema.yml file. Both approaches set properties that dbt applies when building the model.
models: - name: customer_lifetime_value description: "Total revenue and order count per customer, all time." config: materialized: table tags: ["daily", "finance"] columns: - name: customer_id description: "Primary key." tests: - unique - not_null - name: total_spent description: "Sum of all order amounts for this customer."Tags defined here flow through the DAG, so dbt build --select tag:finance will select this model and run it in proper dependency order.
Parallel Execution
dbt executes the DAG level by level. Models at the same level โ those with no dependency on each other โ run in parallel up to the thread limit you configure in profiles.yml.
Execution with 4 threads
Level 1 (parallel): raw_orders, raw_customers โ 2 models, run togetherLevel 2 (parallel): stg_orders, stg_customers โ 2 models, run togetherLevel 3: orders_with_customers โ waits for level 2Level 4: customer_lifetime_value โ waits for level 3Increasing threads speeds up pipelines with wide, independent layers. It has no effect on deep sequential chains.
dbt Packages That Extend DAG Capabilities
Two packages change how you work with the DAG in meaningful ways:
dbt-utils provides macros like union_relations, generate_surrogate_key, and star that reduce repetition in models without changing how the DAG resolves.
dbt-project-evaluator (dbt Labs) runs a set of rules against your DAG structure โ checking for root models without sources, models without tests, fan-out patterns that indicate poor layering. Itโs useful for enforcing DAG hygiene on larger teams.
Common DAG Mistakes
Skipping staging models: Referencing raw source tables directly in mart models creates tight coupling. If the source schema changes, you have to update every downstream model. Staging models act as a translation layer that absorbs those changes in one place.
Using hardcoded schema names: Writing FROM analytics.stg_orders instead of FROM {{ ref('stg_orders') }} means dbt doesnโt register the dependency, your model wonโt appear in the DAG, and the execution order becomes unpredictable.
Overly wide fan-out: If one staging model has 40 downstream dependents, any change to that model causes a cascade rebuild. Intermediate models that group related transformations reduce this blast radius.
The DAG is what makes dbt more than a SQL runner. Itโs the structure that enables reliable execution order, parallelism, targeted rebuilds, and the lineage visibility that makes data pipelines maintainable at scale. Everything else in dbt โ materializations, tests, documentation โ builds on top of it.