The DAG Is the Contract: dbt's Execution Model in One Picture
Ask a room of dbt users what dbt run does and most will say some version of "runs my SQL against the warehouse." That's true, but it skips the step that actually explains dbt's behavior in production: compile happens first, completely, before a single query touches your warehouse.
Compile vs. execute, not SQL vs. Python
When you run dbt build, dbt first resolves every ref() and source() call, renders every Jinja macro, and assembles the full dependency graph — the DAG — into a manifest.json. Only after that graph is fully resolved does execution begin, model by model, in topological order.
This is why a broken ref() to a model that doesn't exist fails at compile time, before any warehouse compute is spent — and why dbt run --select stg_claims+ can decide exactly which downstream models to touch without re-deriving anything at runtime. The DAG isn't a visualization dbt draws for you after the fact. It's the actual execution contract.
-- models/staging/storefront/stg_orders.sql
select
order_id,
customer_id,
cast(order_date as date) as order_date,
channel,
order_status,
order_amount,
cast(updated_at as timestamp) as updated_at
from {{ source('storefront', 'orders') }}
That {{ source(...) }} call isn't a runtime lookup — it's resolved at compile time against sources.yml, which is exactly why a typo in a source name fails your dbt compile step, not a 2am production run three joins downstream.
Where this actually bites people
The engineers who get surprised by dbt in production are usually the ones who think about it query-by-query instead of graph-by-graph: wondering why a materialization change didn't take effect (compile cache), why a --full-refresh behaved differently across environments (adapter-specific incremental gaps), or why a Slim CI run rebuilt half the project for what looked like a one-line change (state:modified comparing more of the compiled node than they expected).
dbt for Data Engineers chapter 1 goes through the full execution model — compile vs. execute, the manifest, materializations and their adapter-specific gaps — free to read right now.