Skip to content

Components Overview

A component is a releasable unit — a service, a table, a cluster, a library — declared as data. It says what it builds, what it produces, and how it deploys, and a single generic orchestrator runs it. New components compose in without new pipelines.

This is the layer above synthesis. chant build turns typed intent into spec-native artifacts. Components turn those artifacts into a release: build once, publish at deploy time, apply, verify, roll back.

Outside Kubernetes, every component tends to drag its own hand-maintained pipeline. Two failures follow, and both compound as you add components:

  • Definition sprawl. N components produce N pipeline definitions that duplicate orchestration with small variations and drift apart. A cross-cutting change — sign every image, add a gate everywhere — means editing every pipeline.
  • Orchestrator glue sprawl. The deploy path accretes custom code per pipeline it has to call. Components get wrapped in brittle, component-specific glue, and adding a component means adding glue.

Kubernetes hid this by collapsing apply into one shared verb (kubectl). Off-cluster there is no shared apply verb, so build, delivery, and apply all vary per component and the variance leaks into pipelines.

The goal is one property:

Pipeline count stops being a function of component count. The 200th component adds zero pipelines and zero edits to the orchestrator.

The insight: unbounded components, bounded primitives

Section titled “The insight: unbounded components, bounded primitives”

Two things are true at once, and holding both is the whole design:

  • Components are unbounded and each is unique. Every one draws its own destiny.
  • The primitive operations underneath are bounded: build an image, promote an artifact by digest, deploy a CloudFormation stack, submit a job, run a command on a host, wait for healthy, roll back. Roughly a dozen verbs, and the set does not grow when you add components.

Sprawl is that bounded set re-expressed as bespoke pipeline code once per component. The fix is to express the primitives once as capabilities and let each component be a unique composition of them. Uniqueness lives in the composition, which is cheap and declarative. Commonality lives in the capabilities, which are coded and tested. The expensive part stops being copied.

Three layers: the deterministic core (chant build) at the base, the build archive on it, and release orchestration — capabilities composed into components — on top. Three layers: the deterministic core (chant build) at the base, the build archive on it, and release orchestration — capabilities composed into components — on top.
Components sit on top of the deterministic core: a generic orchestrator drives declarations composed from a bounded capability set.

Composition, not pipelines

A component declares an ordered composition of capabilities. The orchestrator is generic and contains no per-component code. Adding a component is a declaration, not a pipeline.

Reusable behaviors

Capabilities are a bounded, plugin-style registry of verbs — build, publish, apply, submit, deliver, wait, roll back. Written once, reused across every component of that shape.

Build once, deploy many

Build produces a self-contained archive. Publish is deferred to deploy time and promotes by digest, so the artifact tested in dev is byte-identical in prod. See Build Archive.

Rollback for free

Each capability declares its own compensation. On failure the orchestrator unwinds executed steps in reverse. No hand-written rollback pipelines.

Spec-true, cloud-agnostic

Agnostic in structure and orchestration, spec-true in the leaves. The per-cloud seam is the lexicon boundary. One driver, one lint, real per-cloud deploys.

Zero exotic dependencies

The default runs on plain CI plus the local executor plus native scheduling. Temporal and Dagster are opt-in backends, not baseline. See Orchestration.