How we cut Dagster Cloud credits by 60% by collapsing dbt assets
Learn how we reduced our bill after the May 2026 pricing changes by collapsing our dbt project into a single asset.
In May 2026, Dagster removed the monthly credit allowance from its Solo and Starter tiers.
The change meant Dagster now charged one credit for each step-executed asset materialization, per partition.For teams that used those credits, the change wasn’t small.
A Solo workspace that previously used the full 7,500-credit allowance could go from $10 per month to $310 per month.
A Starter workspace using 30,000 credits could go from $100/month to $1,150/month.
With only a few weeks to react, teams started weighing switching to a self-hosted Dagster, or even migrating to a different orchestrator altogether.
At Narev, we build a Full-Stack AI FinOps Ecosystem, so we spend a lot of time thinking about the cost of running infrastructure.We do like the convenience of Dagster, but our 24/7 schedule quickly drove up our bill.
It actually became more expensive to run Dagster than to run a self-hosted Airflow.
But before committing to a new orchestrator, we wanted to understand any quick wins we could get by optimizing our Dagster setup.
We found the biggest savings in the DBT (data build tool) assets
When we looked at our own Dagster usage, the expensive part wasn’t dbt itself. It was the way the official dagster-dbt integration maps dbt nodes into Dagster assets.The standard pattern uses @dbt_assets, which maps dbt models, and in some setups seeds, snapshots, or sources, into many Dagster graph nodes. That’s useful if you need model-level lineage in the Dagster UI.
But with the new pricing, each asset materialization has an orchestration cost ($0.035 to $0.040 per credit, plus compute time).When you have hundreds of dbt models running hourly across partitions, you will end up paying Dagster to coordinate a graph that dbt already understands.We fixed our setup by collapsing the entire dbt project into one Dagster asset. Same dbt logic. Same data. Far fewer orchestration steps.
This looked very clean. But the billing side effect was significant:
@dbt_assets registers many Dagster graph nodes from your dbt project: Models, and in some setups seeds, snapshots, or sources, expand into Dagster assets. We had hundreds of warehouse views, which meant hundreds of assets in the Dagster graph running hourly.
.stream() with context=context emits per-model materialization events: Dagster records each dbt model as a separate materialization, even though dbt runs them in a single CLI invocation.
A similar problem existed upstream.
Individual loader assets each ran as a separate Dagster step.
Our daily ETL job was coordinating hundreds of assets for work that was really just “load raw tables, then run dbt build.”
The gotcha: context=context defeats the whole point
Our first version of the single-asset approach still passed context to the CLI:
# Do not do this if you want to save Dagster creditsresult = dbt_resource.cli(args, context=context).wait()
Do not pass context=context to dbt_resource.cli() if your goal is to save credits. Even inside a single @asset, this tells dagster-dbt to emit individual materialization events for each dbt model. You end up paying for per-model orchestration without getting per-model assets.
The fix is one line:
result = dbt_resource.cli(args).wait()
No context, no per-model events, one materialization per partition per day.
Downstream assets that previously depended on individual dbt output models now depend on the single transform asset:
@dg.asset( deps=[dg.AssetKey(["dw_transform"])],)def clickhouse_report_alerts( context: dg.AssetExecutionContext, clickhouse_resource: ClickHouseResource) -> pd.DataFrame: client = clickhouse_resource.get_client() # ... query the table dbt already built in ClickHouse
The activator does not care which dbt models ran. It reads from the table in the warehouse. dbt did its job. Dagster just needed to know “transform is done.”
This is not a free optimization. By collapsing our pipeline, we gave up a few Dagster features. Be honest about these trade-offs before adopting this pattern:
No per-model lineage in Dagster: You will not see staging/_stg_openrouter as its own node in the asset graph. Use dbt docs, Elementary, or your warehouse for model-level lineage.
No per-model Dagster alerting: If one dbt model fails, the whole dw_transform asset fails. dbt logs still tell you which model broke; you just do not get a Dagster alert scoped to that specific model.
Coarser observability: One green checkmark for the entire transform layer instead of hundreds.
For many teams, dbt already owns transform observability. Dagster’s job is orchestration: “extract done → load done → transform done → activate.” You may not need your orchestrator to also be your catalog.
The multi-asset pattern is still the right choice if:
You are on Dagster Cloud Hybrid or self-hosted where orchestration cost is not billed per-step.
You are heavily leveraging Dagster 1.13’s virtual assets (which do not consume credits).
You absolutely need Dagster-native lineage across dbt models and non-dbt assets at the model level.
Different dbt models have different schedules or owners and genuinely need independent materialization.
Your dbt project is small (under ~15 models) and the cost difference is negligible.
But if you are on Dagster Cloud with a large dbt project and a predictable “run everything daily” pattern, the single-asset approach is likely cheaper.