Skip to content

Channels — one pipeline, many tenants

The question for this lesson: you have one customer_etl pipeline, and several customers (tenants) want the same logic with one number changed. Acme Corp only calls someone “gold” at a much higher lifetime value than everyone else. Do you copy the whole pipeline and edit a line for each tenant? No. You keep one pipeline and add a small channel overlay per tenant.

  • Read a channel overlay and say what it overrides, and what it can’t.
  • Override a pipeline’s declared variable for one deployment, without touching its logic.
  • Apply a channel at run time with --channel.

A channel is a cataloged deployment identity with a manifest and per-target overlay files. This lesson changes only a declared variable. The broader system also supports explicit structural operations such as schema patches and composition injection; those are validated during planning. A variable override itself does not edit CXL or rewire nodes.

It works in two halves. First, the base pipeline declares a variable with a default:

pipeline:
name: customer_etl
vars:
gold_threshold: { type: int, default: 10000 } # the lifetime-value cut for "gold"

and reads it in a CXL expression with $vars:

emit tier = if lifetime_value.to_int() > $vars.gold_threshold then "gold" else "standard"
🌱 New here? — $vars

$vars.gold_threshold reads the value of the gold_threshold variable declared at the top of the pipeline. Declaring a number once, by name, instead of hardcoding 10000 inside the expression, is exactly what lets a channel change it later without editing the logic.

Put customer_etl.yaml in a workspace root and declare the catalog in clinker.toml:

[catalog.pipelines]
customer_etl = "customer_etl.yaml"
[catalog.channels]
acme-corp = "channel/acme-corp"

Create channel/acme-corp/channel.cfg.yaml with channel: { name: acme-corp, targets: [customer_etl] }. Then the per-target overlay supplies the different value:

channel/acme-corp/customer_etl.channel.yaml
channel:
target: customer_etl # logical ID from catalog.pipelines
vars:
static:
gold_threshold:
type: int
default: 50000 # Acme's higher bar for "gold"

and you apply it at run time with --channel:

clinker run customer_etl.yaml --channel acme-corp --base-dir .

Select a channel ID, not an overlay filename. The catalog resolves the manifest and the overlay whose channel.target names this pipeline’s catalog ID. Here only gold_threshold changes. Inspect the resolved configuration with clinker channels resolve customer_etl.yaml --channel acme-corp --base-dir . before running it.

First, the base on its own, with no channel, so gold_threshold is its declared default, 10000. Predict each customer’s tier, then run.

pipeline.yaml // editable

Only the 80000 customer clears 10000, so only they are gold.

Acme’s channel sets gold_threshold to 50000. The pipeline is identical; only that one number changes.

Add bright-start = "channel/bright-start" under [catalog.channels] in clinker.toml. Create channel/bright-start/channel.cfg.yaml containing channel: { name: bright-start, targets: [customer_etl] }, then write its per-target overlay. Bright Start is a tenant that rewards smaller customers: anyone over 3000 lifetime value should count as gold. Write its channel file.

// quick check

What does Acme's channel actually change in the pipeline?