dbt™ Defer to Production: Save Time and Resources
Feb 26, 2026
dbt™ Defer to Production: Save Time and Resources
Every dbt™ project eventually hits a wall: development runs take too long, warehouse bills keep climbing, and dev schemas fill up with tables nobody asked for. The culprit is almost always the same—rebuilding upstream models that haven't changed just to test one small edit downstream.
dbt™ defer solves this by letting developers point unchanged models to production instead of rebuilding them from scratch. The result is faster iteration, lower compute costs, and cleaner development environments.
This guide covers what dbt™ defer to production is, why teams adopt it, exactly how it works under the hood, and how to set it up—whether you're running dbt Core™ on the command line or using a managed platform like Paradime.
What is dbt™ defer to production
dbt™ defer is a feature that lets developers reference production models instead of rebuilding the entire upstream DAG during development. When you enable defer, any model you haven't modified resolves to its existing production version using a previously generated manifest file—so dbt™ skips building it entirely.
Here are the core concepts:
Defer: In the dbt™ context, defer means instructing dbt™ to resolve
ref()calls for unselected or unchanged models against a production (or staging) environment instead of your local dev target. Rather than materializing every upstream dependency, dbt™ reads directly from tables that already exist in production.Production manifest: The
manifest.jsonfile is the artifact that contains metadata about your production models—database names, schema names, compiled SQL, model configurations, and lineage relationships. This file is generated every time you rundbt build,dbt run, ordbt compileand is stored in thetarget/directory. When you defer, dbt™ compares your current project against this manifest to determine what already exists in production.Upstream models: These are the parent models your current work depends on. In a typical DAG, a reporting model might depend on staging models, which depend on source tables. Without defer, dbt™ would rebuild every one of those upstream parents. With defer, only the models you've actually changed get built—everything else points to production.
Why dbt™ teams use defer to production
Reduce warehouse compute costs
Without defer, every dbt run rebuilds all upstream models—even the ones that haven't changed. For a project with hundreds of models, that means scanning and materializing tables repeatedly just to test a single downstream edit.
Defer eliminates this redundant compute by reusing existing production tables for any model that hasn't been modified. Instead of running the full DAG, dbt™ only processes the models you're actively working on.
This matters especially for teams running large DAGs on warehouses like Snowflake, BigQuery, or Databricks, where compute is priced by the second or by data scanned. Cutting unnecessary upstream builds can reduce development warehouse costs dramatically.
Speed up development iteration cycles
Developer experience improves the moment build times drop. Without defer, you might wait 10–30 minutes for a full pipeline build just to validate a column rename or a filter change. With defer enabled, you only build what you're actively changing—and the feedback loop shrinks from minutes to seconds.
This faster iteration cycle means developers can test more frequently, catch issues earlier, and ship changes with more confidence. Instead of batching multiple changes into a single long-running build, each change gets its own fast validation.
Maintain cleaner development environments
Without defer, every model in your DAG gets materialized in your dev schema during a full build. Over time, this creates schema clutter—dozens or hundreds of tables and views that don't reflect your actual changes, many of which become orphaned as branches are abandoned.
Defer reduces this noise by only materializing the models you've modified. Unchanged models never appear in your dev schema because they're read directly from production. The result is a development environment that contains only the artifacts relevant to your current work—making it easier to navigate, debug, and clean up.
Improve testing and validation accuracy
When you test against a full dev rebuild, your upstream data may be stale, synthetic, or incomplete. Seeds might not reflect real-world volume, and incremental models in dev often lack the history that production tables carry.
With defer, your unchanged upstream models point to real production data. This means your tests and validations run against the same data that your production dashboards consume—giving you more realistic and reliable results before you merge.
How dbt™ defer resolves model references
The ref function and deferred resolution
dbt™'s ref() function is how you reference one model from another. Normally, it resolves to your current target schema—so {{ ref('stg_orders') }} compiles to something like dev_alice.stg_orders.
With defer enabled, the resolution logic changes. For any model that meets both of these conditions:
It is not among the selected nodes in your current run
It does not exist as a database object in your dev environment (or
--favor-stateis used)
…dbt™ resolves the ref() call using the production manifest instead. So {{ ref('stg_orders') }} compiles to prod.stg_orders rather than dev_alice.stg_orders.
Here's what that looks like in practice:
You write your model the same way you always would. The magic happens at resolution time—dbt™ decides where each ref() points based on whether you've modified the referenced model and whether it exists in dev.
Note: Ephemeral models are never deferred, since they serve as passthroughs for other
ref()calls and don't exist as database objects.
How dbt™ resolves a ref() call when defer is enabled—checking selection and dev existence before falling back to production.
How the production manifest powers defer
The manifest.json file is the backbone of dbt™ defer. It's an artifact generated during any dbt™ command that parses your project (dbt build, dbt run, dbt compile, etc.) and is saved to the target/ directory.
The manifest contains a full representation of your dbt™ project's resources:
nodes: All models, seeds, snapshots, tests, and analyses with their unique IDs, SQL bodies, and configurations
sources: All declared sources with database and schema metadata
macros: Every macro in your project and its compiled logic
parent_map and child_map: The lineage relationships between nodes
database, schema, and alias: The exact production location of each model
When you pass a production manifest via the --state flag, dbt™ reads this file to know where each production model lives. If stg_orders is recorded in the manifest as analytics_prod.stg_orders, that's exactly where the deferred ref() will point.
This is why keeping your production manifest current matters—dbt™ trusts whatever the manifest says. If the manifest is stale or missing a model, defer can't resolve correctly.
State comparison for modified model selection
The state:modified selector is how dbt™ compares your current project state against the production manifest to identify which models have actually changed. This is what connects defer to the "slim" execution pattern.
When you run:
dbt™ parses your current project, loads the production manifest from prod-artifacts/, and compares every node. A model is considered "modified" if any of the following changed:
Body (
state:modified.body): The model's SQL or seed values changedConfigs (
state:modified.configs): Node configurations changed (excluding database/schema/alias)Relation (
state:modified.relation): Database, schema, or alias changedMacros (
state:modified.macros): Any upstream macro (direct or indirect) changedContract (
state:modified.contract): Model contract columns changed
You can also use the + graph operator to include downstream dependents:
This runs every modified model plus all of its downstream children—while deferring everything upstream to production. It's the foundation of slim CI pipelines.
How to configure dbt™ defer to production
1. Generate and store your production manifest
Every time you run a dbt™ command in your production environment, it generates a manifest.json file in the target/ directory. This file is what dbt™ needs to resolve deferred references.
To generate it explicitly:
After the run completes, copy the manifest to a dedicated location:
For team workflows, you'll want to store this manifest somewhere accessible:
Cloud storage: Upload to S3, GCS, or Azure Blob after each production run
CI artifact: Store as a build artifact in GitHub Actions, GitLab CI, or CircleCI
Shared directory: Use a mounted network drive or shared volume
The key requirement is that every developer and CI job can access the latest production manifest at a known, consistent path.
2. Set up your development environment
Your profiles.yml should define separate targets for development and production. This ensures dbt™ knows which schema to use for each environment:
The separation is important: your dev target uses a personal schema (like dev_alice), while the production manifest references models in the prod schema (like analytics_prod). When defer is active, unmodified ref() calls resolve to the prod schema locations recorded in the manifest.
Important: Developers need read access to the production database and schema referenced in the manifest. Without it, deferred queries will fail with permission errors.
3. Run dbt™ commands with the defer flag
With your manifest in place and your dev target configured, you can run any dbt™ command with defer:
Here's what each flag does:
Flag | Purpose |
|---|---|
| Only build the specified model (and optionally its dependents with |
| Enable deferred resolution—unmodified refs point to production |
| Path to the directory containing the production |
You can combine defer with the state:modified selector for maximum efficiency:
This builds only the models you've changed plus their downstream dependents, while deferring everything else to production. It works with dbt run, dbt test, dbt build, dbt compile, and dbt seed.
If you want dbt™ to always prefer the production manifest—even when a model exists in your dev schema—add the --favor-state flag:
4. Automate manifest replication across your team
Manually copying manifest.json files doesn't scale beyond a single developer. For team workflows, you need automated manifest distribution.
GitHub Actions example:
CI job that consumes the manifest:
Alternatively, platforms like Paradime handle manifest syncing, storage, and deferred execution automatically—eliminating the need to build and maintain this infrastructure yourself.
How production manifests flow from CI to developers, enabling deferred runs against production data.
Best practices for dbt™ defer to production
Use environment variables for dynamic configuration
Hardcoding the --state path in every command is fragile—it breaks when developers use different directory structures or when CI runners have different file paths.
Instead, use the DBT_STATE and DBT_DEFER environment variables:
With these set, you can run dbt™ commands without specifying flags every time:
This approach makes your configuration portable across local development, CI environments, and different team members—everyone sets the env vars to their local manifest path, and the commands work identically.
For teams that need to separate the manifest used for state comparison from the one used for deferral, there's also DBT_DEFER_STATE:
Avoid stale production data issues
Defer relies on production data being current. If your production pipeline hasn't run recently, deferred ref() calls will point to outdated tables—which means your development tests may pass against data that no longer reflects reality.
To mitigate this:
Keep production runs frequent. Daily runs are a minimum for most teams; high-velocity data teams may run production hourly.
Timestamp your manifests. Include the generation timestamp in your manifest storage path (e.g.,
s3://artifacts/prod/2024-01-15/manifest.json) so developers can verify freshness.Use
--favor-statecarefully. This flag forces dbt™ to always use the production manifest, even when a model exists in dev. It's powerful but means you're always reading from production—if production is stale, so is your dev data.Clear dev schemas when starting a new branch. Leftover tables from previous branches can cause dbt™ to resolve refs to stale dev objects instead of deferring to production. Starting clean avoids this pitfall.
Apply defer to more than just dbt™ run
Defer isn't limited to dbt run. It works with every dbt™ command that resolves ref() calls:
Using defer consistently across all development commands ensures your entire workflow benefits from faster builds and accurate data. If you only defer during dbt run but not dbt test, your tests may still attempt to reference non-existent dev tables.
How dbt™ defer powers slim CI pipelines
What is slim CI in dbt™
Slim CI is a CI/CD strategy where you run only the models that have been modified in a pull request—plus their downstream dependents—instead of rebuilding the full project. It dramatically reduces CI build times and warehouse costs.
Without slim CI, every pull request triggers a full project build. For a project with 500 models, that might mean 30+ minutes of compute time and significant warehouse spend—even if the PR only changed a single model.
With slim CI, that same PR builds only the modified model and its downstream children. If only 3 models are affected, only 3 models get built and tested. Everything else is deferred to production.
Full CI rebuilds every model (red). Slim CI defers unchanged models to production (green) and only builds affected models (yellow).
How defer accelerates CI build times
Slim CI uses defer as its foundation. The typical command in a CI pipeline looks like this:
Here's what happens step by step:
State comparison: dbt™ loads the production manifest from
prod-artifacts/and compares it against the current branch's project stateSelection:
state:modified+selects every model whose SQL, config, or upstream macros changed, plus all downstream dependentsDeferral: For every model not in the selection,
ref()calls resolve to the production schema recorded in the manifestExecution: Only selected models are built and tested—against real production data for upstream dependencies
Teams that adopt slim CI regularly see CI build times drop from 30+ minutes to under 5 minutes. One team reported a 10x improvement—going from 50-minute CI runs to 5 minutes after implementing state:modified with defer.
Paradime's TurboCI is an implementation of this pattern that adds column-level lineage diff on top—so you can see not just which models changed, but which specific columns in downstream models and dashboards are affected by a PR.
Run dbt™ defer to production automatically with Paradime Bolt
Setting up defer manually—generating manifests, syncing them to shared storage, configuring CI pipelines, managing environment variables—works, but it introduces operational overhead that grows with team size.
Paradime Bolt handles manifest management, deferred runs, and slim CI automatically without manual configuration:
Automatic manifest syncing: Production manifests are always current and accessible. Every time a Bolt schedule runs in production, the manifest is automatically stored and made available to all developers and CI jobs—no S3 buckets or GitHub artifacts to manage.
One-click deferred runs: Toggle defer on or off directly in the Paradime Code IDE. No CLI flags, no
--statepath management, no environment variable setup. Select standard defer or--favor-statemode from the editor toolbar, and every command you run automatically defers to production.TurboCI integration: Slim CI with column-level lineage diff built in. TurboCI uses
state:modified+selection with defer under the hood, and adds impact analysis showing exactly which downstream models and dashboard columns are affected by each pull request.dbt Cloud™ migration: Teams can import existing dbt Cloud™ jobs, schedules, and environments into Paradime Bolt using the built-in importer—and enable defer instantly without reconfiguring pipelines or missing scheduled runs.
FAQs about dbt™ defer to production
Does dbt™ defer work with incremental models and snapshots?
Yes. Defer works with all materialization types—tables, views, incremental models, snapshots, and materialized views. The deferred reference points to the production version of the model regardless of its materialization strategy.
When dbt™ defers an incremental model, it resolves the ref() to the production table containing the full incrementally-built dataset. Your development model reads from that complete production table rather than an incomplete dev version. The same applies to snapshots—deferred refs point to the production snapshot table with its full history.
Can I use dbt™ defer to reference a specific schema instead of full production?
Yes. Defer references whatever environment generated the manifest file you provide via --state. It's not limited to production—you can point to staging, QA, or any other environment by using that environment's manifest.json.
For example, if you want to defer to a staging environment:
As long as staging-artifacts/manifest.json was generated from a run against your staging environment, all deferred ref() calls will resolve to staging schema locations.
You can even use --defer-state to separate the manifest used for state comparison from the one used for deferral:
This compares state against ci-artifacts/manifest.json but defers refs to prod-artifacts/manifest.json.
How do I troubleshoot when dbt™ defer is not resolving refs correctly?
Common issues and their solutions:
Stale or missing manifest file: Verify your manifest is current by checking its modification timestamp. If production hasn't run since you made upstream changes, the manifest won't reflect the latest state. Regenerate it with
dbt compile --target prod.Incorrect
--statepath: The--stateflag should point to the directory containingmanifest.json, not the file itself. Use--state prod-artifacts/, not--state prod-artifacts/manifest.json.Model doesn't exist in the comparison state: If a model is brand new (not yet in production), it can't be deferred because it has no production counterpart. Build it in dev first, or add it to the selection criteria.
Dev version exists unexpectedly: If a table from a previous branch still exists in your dev schema, dbt™ may resolve to it instead of deferring to production. Use
--favor-stateto always prefer the manifest, or drop stale dev tables.--stateand--target-pathconflict: dbt™ overwritesmanifest.jsonduring parsing. If--statepoints to the same directory as--target-path, the production manifest gets overwritten. Use a separate directory for your production artifacts.
What is the difference between defer in dbt Core™ and managed platforms like Paradime?
The underlying defer mechanism is identical—both use the --defer flag and manifest comparison to resolve ref() calls. The difference is in operational overhead:
Aspect | dbt Core™ | Paradime |
|---|---|---|
Manifest generation | Manual ( | Automatic after every Bolt schedule run |
Manifest storage | Self-managed (S3, GCS, CI artifacts) | Built-in, always accessible |
Manifest distribution | Custom CI/CD scripts | Automatic sync to IDE and CI |
Enabling defer | CLI flags ( | One-click toggle in Code IDE |
Slim CI setup | Custom GitHub Actions/GitLab CI config | TurboCI with column-level lineage diff |
Environment management | Manual | UI-based connection and environment config |
dbt Core™ gives you full control but requires you to build and maintain the infrastructure around defer. Managed platforms like Paradime abstract that infrastructure away so teams can focus on writing and testing models instead of managing manifests and CI pipelines.