Skip to content

Deployment & CI/CD

You're getting dbt Mesh at the same time as this topic, so treat them as one problem: a two-project setup only really works in production if your CI/CD strategy accounts for the dependency between them. This topic assumes you're comfortable with what state:modified+ and deferral do for a single project - the focus here is the mesh-specific design work.

Exercise

mediapulse_analytics depends on mediapulse_base (never the reverse) via dependencies.yml. That one-directional dependency should shape everything about how you sequence CI.

Step 1 - Map the dependency direction onto job order

  • Step complete

Write out, as a numbered list, which project's CI/CD jobs need to exist and in what order they'd have to run for a change that touches both projects to be safely validated end-to-end. Be explicit about which job outputs the next job depends on.

Hint: What deferral actually needs

A downstream job can only defer sensibly to state that's already been published. Read Deployment environments and Defer - deferral compares against a specific environment's last successful manifest, so the order you build and promote environments in is part of your CI design, not an implementation detail.


Step 2 - Identify what triggers each job

  • Step complete

For each job in your list, state its trigger (PR opened against which project, merge to main, schedule, or "runs because another job just finished"). Not every job in a mesh should be PR-triggered on its own project alone.


Step 3 - Decide what "modified" means across the boundary

  • Step complete

state:modified+ only looks within one project's manifest by default. If stg_ads__campaigns changes in mediapulse_base, what needs to happen for mediapulse_analytics's CI to even know that occurred, given fct_ad_revenue depends on it? Read About dbt Mesh for how cross-project ref() resolution actually works at build time, and use that to justify your answer rather than assuming state comparison "just works" across projects.


Step 4 - Write the job matrix

  • Step complete

Produce a simple table: job name, project, trigger, selector (in plain English), and what it defers to.


Step 5 - Encode two rows of the matrix as a real selectors.yml

  • Step complete

Create a selectors.yml file at the root of mediapulse_analytics. Turn at least two rows of your job matrix into real named selectors - one that would run for a PR touching only mediapulse_analytics, and one that reflects a change surfacing from mediapulse_base.

Hint: selectors.yml shape

A selectors.yml sits at the project root next to dbt_project.yml, with a top-level selectors: list. Each entry has a name and a definition - either a simple CLI-style string (state:modified+) or a fuller method/value object with graph operators. See YAML selectors for the full syntax, including how union/intersection/exclude combine multiple criteria if one of your rows needs that.


Deliverable: Your job matrix from step 4, the selectors.yml file from step 5, and a short justification for the ordering you chose in step 1.

Extension

Now bring governance into it. mediapulse_base's staging models are already access: public (as is the streaming marts folder) - meaning mediapulse_analytics is allowed to ref() them across the project boundary. Right now, nothing stops someone from changing one of those public models' shape in a way that silently breaks mediapulse_analytics.

Step 1 - Find out what dbt actually automates here

  • Step complete

Read Model contracts and About model governance closely. Answer precisely: does dbt natively detect a breaking change to a public model during CI, and if so, under what conditions (enforced contract? model version? which dbt version introduced which check)? Don't round this up to "dbt handles it" if the real answer has caveats.


Step 2 - Apply it to a real model

  • Step complete

Enforce a contract on stg_ads__campaigns in mediapulse_base. In _ads__models.yml, add config: contract: enforced: true to the model and declare a data_type for every one of its columns - not just the ones that currently have tests or descriptions. Check what type each column actually lands as in the warehouse before you type it (this model does no casting at all, so at least one column's real type will probably surprise you). Run dbt build --select stg_ads__campaigns and confirm it still passes with the contract in place.

Hint: contracts require every column, typed

Once a model has contract: enforced: true, dbt requires a data_type for every column it selects, not just the documented or tested ones - and it will fail to build if the declared types don't match what actually comes back from the warehouse. See Model contracts for the exact YAML shape and which materializations support it.


Step 3 - Reason about the cost

  • Step complete

Now that you've done it: what would you have to commit to about stg_ads__campaigns's columns going forward? What's the practical cost of that commitment given the model still has known gaps elsewhere in the project (recall stg_news__articles doesn't deduplicate on article_id - would you enforce a contract there too, or not yet)?


Step 4 - Design the guardrail

  • Step complete

Using what you learned in step 1, describe exactly what would need to be true (contract enforced, versioned or not, dbt version) for a PR that removes or retypes a column on a public mediapulse_base model to fail CI automatically because mediapulse_analytics depends on it - versus what would only produce a warning, versus what dbt won't catch at all and has to be a team convention instead (e.g. a required reviewer from the consuming team).


Step 5 - Recommend a rollout order

  • Step complete

Contracts and versioning add real friction to actively-changing models. Read Coordinating model versions and propose which of mediapulse_base's currently-public models (if any) are stable enough to contract now, and which you'd deliberately leave uncontracted for now - with reasons.


Deliverable: A precise answer to step 1 (what's native vs. convention), the contract you enforced on stg_ads__campaigns in step 2, your cost analysis from step 3, and your rollout recommendation from step 5.

Done?

You've designed a real job matrix for a two-project mesh and enforced a contract on a live model, distinguishing what dbt actually automates from what your team still has to own as process.

Now head to dbt Mesh to map this mesh's real dependency graph from the producer side.