Overview
ThePrefectDbtOrchestrator gives Prefect full control over dbt DAG execution. Instead of running dbt build as a single batch operation, the orchestrator parses your manifest, computes execution waves, and runs each node or wave as a Prefect task — enabling per-node retries, Prefect-native concurrency control, automatic downstream skipping on failure, and fine-grained observability.
If you instead want dbt to drive execution with Prefect observing from the outside, see PrefectDbtRunner.
Install
PrefectDbtOrchestrator first shipped in prefect-dbt 0.7.17, but this guide exercises APIs that landed later. Pin prefect-dbt>=0.7.23 to match every example on this page:
See the integration overview for adapter-specific extras.
Quick start
run_build() automatically runs dbt parse to generate manifest.json if none is found in the project’s target directory. Pass manifest_path directly to the orchestrator constructor to use a pre-built manifest (e.g. from a CI artifact).When the default Always pass
DbtCoreExecutor has to generate a manifest itself (no manifest.json at the target path and no manifest_path passed), it first runs dbt deps to install packages declared in packages.yml, then dbt parse. If you pass a pre-built manifest_path or supply a custom executor, this does not happen — make sure your image already has the required dbt packages installed. To keep the default executor but skip the automatic dbt deps:settings= alongside a custom executor= — the orchestrator itself still uses settings.project_dir and settings.profiles_dir for selector resolution, source freshness, and artifact paths. Omitting it makes the orchestrator fall back to a default PrefectDbtSettings(), which only works when the current working directory already contains a valid dbt project and profile.run_build() raises DbtBuildFailed if any node finishes with "error" status, so a failing dbt build fails the enclosing flow run. Pass raise_on_failure=False to the orchestrator to inspect the results dict on partial failures instead.
Execution modes
PER_WAVE (default)
Each wave of independent nodes runs as a single dbt build invocation. Lowest overhead. Nodes inside a wave that finish with per-node artifacts still appear in the results dict with their real status — only the nodes that actually failed are marked "error" — but once any node in a wave fails, all downstream waves are skipped as a group.
PER_NODE
Each node becomes a separate Prefect task in its own subprocess. Failed nodes retry independently; other nodes in the same wave continue; downstream dependents of failed nodes are skipped.
Result format
run_build() returns a dict mapping unique_id to a result. Each result has a "status" of "success", "cached", "error", or "skipped":
Test strategies
Thetest_strategy parameter controls when dbt tests run relative to models. The default is IMMEDIATE, matching dbt build semantics.
Caching
Enable cross-run caching to skip nodes whose SQL, config, and upstream dependencies haven’t changed since the last successful run. Only available inPER_NODE mode.
Pass a CacheConfig instance to consolidate all caching options:
- the hash of the node’s source SQL (or CSV for seeds) — not the compiled SQL, so config changes that only affect compilation via vars or environment will not invalidate the key on their own
- the node’s dbt config
- the node’s relation name
- the
full_refreshflag - the macros the node depends on (each macro’s content is hashed and folded in)
- upstream nodes’ cache keys (changes cascade downstream)
"status": "cached" (see Result format above).
CacheConfig fields
Custom cache storage
Block slugs follow the format"block-type/block-name" (for example "s3-bucket/my-results"). The block must be saved in Prefect before use. See How to persist workflow results for a walkthrough on creating and saving storage blocks.
Cache expiration from source freshness
Whenuse_source_freshness_expiration=True, each node’s cache TTL is derived from the freshness thresholds of its upstream sources — a node backed by an hourly source won’t be cached longer than the source’s warn threshold.
use_source_freshness_expiration overrides the global expiration on a per-node basis.Dry-run with plan()
Preview what run_build() would execute without actually running dbt models. The plan() method performs manifest parsing, selector resolution, and wave computation, but only invokes dbt ls (for selector resolution) and dbt source freshness (when enabled) — no models are built.
plan() is not free on the first call. With the default DbtCoreExecutor, if no manifest.json exists at the target path and no manifest_path was passed, the executor will run dbt deps followed by dbt parse before wave computation. To make plan() cheap, point manifest_path at a pre-built manifest (e.g. a CI artifact).BuildPlan includes:
plan() accepts the same parameters as run_build(): select, exclude, full_refresh, only_fresh_sources, target, and extra_cli_args.
Artifacts and asset tracking
Summary artifact
When invoked from inside a flow run,run_build() creates a Prefect markdown artifact summarizing results — nodes executed, statuses, and timings. You can optionally write a dbt-compatible run_results.json:
include_compiled_code=True does not affect the summary artifact — the artifact is rendered from the results dict alone. The flag currently only adds compiled SQL to the asset descriptions attached to each MaterializingTask in PER_NODE mode.- The summary artifact is created with key
dbt-orchestrator-summary. Creation requires an active flow run context — if you callrun_build()from a plain Python script (supported inPER_WAVEmode), the summary step is skipped even withcreate_summary_artifact=True. run_results.jsonis written insidesettings.target_path(defaults to<project_dir>/target/run_results.json, but moves withtarget_pathor a custommanifest_path).
ArtifactFilter — read_artifacts ignores unknown kwargs, so a bare key=... argument silently returns every artifact in the workspace:
The artifact key
dbt-orchestrator-summary is fixed and not currently configurable.Prefect asset lineage
InPER_NODE mode, each model, seed, and snapshot creates a Prefect asset and tracks upstream lineage automatically — integrating dbt’s data lineage into Prefect’s asset catalog.
Assets are named using the relation name (for example snowflake://my_db/analytics/stg_users). Upstream assets, including dbt sources, are tracked as asset dependencies.
To disable asset creation:
run_build() parameters
target
Override the dbt target at run time without changing profiles.yml:
extra_cli_args
Pass additional CLI flags directly to dbt invocations. Useful for flags not exposed as first-class orchestrator parameters:
State-based execution (CI/CD)
Use dbt’s state flags to run only modified models and defer to production for everything else:defer_state_path and favor_state for more advanced deferral scenarios.
dbt Cloud execution
TheDbtCloudExecutor lets you run dbt nodes via dbt Cloud ephemeral jobs instead of local dbt-core. The orchestrator creates a temporary job in dbt Cloud for each node (or wave), polls until completion, fetches results, and deletes the job — giving you all orchestrator features (retries, caching, test strategies, assets) backed by dbt Cloud’s managed infrastructure.
Credentials setup
Create or load aDbtCloudCredentials block with your dbt Cloud API key and account ID:
Quick start
Even with a
DbtCloudExecutor, the orchestrator still reads settings.project_dir and settings.profiles_dir for dbt build --select resolution, source-freshness handling, and artifact paths. Pass an explicit settings=PrefectDbtSettings(...) pointing at a local checkout of the same project — omitting it only works when the flow already runs inside a valid local dbt project with a usable default profile. The orchestrator’s state_path, defer, defer_state_path, and favor_state parameters, however, are specific to DbtCoreExecutor and have no effect when a custom executor is used.Finding your dbt Cloud IDs
account_id— Settings → Account Settings → Account ID (also visible in your dbt Cloud URL:cloud.getdbt.com/deploy/{account_id}/...).project_id— Deploy → Jobs → select any job → the URL contains/projects/{project_id}/.environment_id— Deploy → Environments → select the target environment → the URL contains/environments/{environment_id}.defer_to_job_id— Deploy → Jobs → select your production job → the URL ends with/jobs/{job_id}.
Parameter reference
Manifest resolution
The executor needs amanifest.json to parse the dbt DAG. How it gets one depends on defer_to_job_id:
defer_to_job_idis set — fetchesmanifest.jsonfrom the most recent successful run of that job viaGET /jobs/{job_id}/artifacts/manifest.json. This is the recommended approach for production — point it at your production job to reuse its compiled manifest.defer_to_job_idisNone— creates an ephemeraldbt compilejob, runs it, fetches the resulting manifest, and deletes the job. Useful for CI or when you need a fresh manifest, but adds compilation time.
Performance notes
- Connection pooling. In
PER_NODEmode, dbt adapter connections are automatically pooled and reused across sequential node invocations within each worker process, reducing connection overhead. - Eager DAG scheduling. In
PER_NODEmode, nodes are submitted to the task runner as soon as all their individual dependencies complete, rather than waiting for wave boundaries. This maximizes concurrency without any configuration changes.
Comparison with PrefectDbtRunner
Both coexist in the same project —
PrefectDbtRunner is unchanged. See PrefectDbtRunner for its dedicated guide.