CNCF's March 2026 case for Crossplane makes a specific claim: infrastructure only becomes something an AI agent can safely operate once it's expressed as a discoverable, watchable Kubernetes API instead of a pile of provider-specific SDK calls. It's a good argument. It's also not new to platforms that already run on Cluster API — and that overlap is the actual question this post answers: does a self-hosted PaaS built on Cluster API need to bolt a Crossplane XRD layer on top, or is that solving a problem its own API already solves?
Short answer: no, not for the machine-lifecycle layer — CAPI already is that API. Yes, conditionally, for anything that isn't a machine. Here's the concrete comparison that gets you to that line instead of "it depends."
Two Different Answers to the Same Question
Both projects answer "expose infrastructure as a Kubernetes-native API" — they just picked opposite ends of the generic-to-specific spectrum.
Crossplane is a composition engine. A CompositeResourceDefinition (XRD) declares the schema for a new custom API — in Crossplane v2, namespaced by default, so a tenant's claim lives in its own namespace instead of leaking cluster-wide. A Composition is a pipeline of composition functions that Crossplane calls whenever someone creates a resource matching that schema; the functions decide what graph of underlying Managed Resources to create. The schema can describe anything: a Postgres instance, an S3-compatible bucket, a DNS record, or — because Crossplane doesn't care what's on the other end of the API — a whole Kubernetes cluster.
Cluster API doesn't generalize. It ships three purpose-built resources — Machine, MachineSet, MachineDeployment — that map directly onto Kubernetes' own Pod/ReplicaSet/Deployment pattern, aimed at exactly one job: turn "I want N healthy nodes running this Kubernetes version" into reconciliation against real infrastructure providers (Hetzner via CAPH, bare metal via Metal3, the major clouds). There's no schema to define because the schema is fixed — a Machine is always a machine.
| Crossplane (XRD + Composition) | Cluster API | |
|---|---|---|
| What a "claim" creates | Whatever graph of resources the Composition's functions decide — a bucket, a DB, a DNS record, a cluster | Always the same thing: a provisioned, health-checked node joined to a cluster |
| Schema | Defined per-platform via XRD — arbitrary | Fixed — Machine, MachineSet, MachineDeployment |
| New capability = | Write an XRD + Composition, no new controller code | Not applicable — CAPI doesn't add new resource types, only new infrastructure providers (CAPH, Metal3, CAPA…) |
| Failure/health model | Whatever the Composition's functions implement | Built-in: Machine status conditions (Ready, NodeHealthy), automatic replacement on failure |
That's the actual axis: Crossplane trades a fixed schema for the ability to mint new ones on demand; Cluster API trades that flexibility for a narrower API it can make deeply reliable at the one thing it does.
The Case For: What Crossplane Buys That CAPI Structurally Can't
Cluster API has no answer to "a tenant wants a Postgres database" or "a tenant wants an S3-compatible bucket with a lifecycle policy" — those aren't machines, so they're out of scope by design. That's exactly the gap Crossplane's genericity fills, and it's the strongest form of the CNCF argument: instead of a platform engineer hand-writing a controller for every new resource type a tenant might want, you write an XRD once and let Composition functions do the provisioning.
A tenant-facing bucket claim looks roughly like this once the XRD exists:
apiVersion: platform.example.io/v1alpha1
kind: Bucket
metadata:
name: uploads
namespace: team-checkout
spec:
region: fsn1
public: falseApply that, and the Composition's function pipeline resolves it into whatever Managed Resources actually provision the bucket. The tenant — or an agent acting on the tenant's behalf — never touches a provider SDK. It watches one object's status field for Ready: true, the same way it would watch a Machine. That's the part of the CNCF piece worth taking seriously: a single, discoverable, watchable API surface really does remove a category of work an AI agent would otherwise have to do by orchestrating across a Kubernetes API and a cloud provider's API and a DNS provider's API, each with its own auth and error semantics.
Crossplane graduated as a CNCF project on the strength of exactly this pattern being reused across hundreds of platform teams, and the newest demonstrations lean into the AI-operability argument directly — a March 2026 CNCF Cloud Native Live session showed an LLM reading real Composition status and metrics fields to make scaling decisions against a database control plane, then writing its decision back as a normal Kubernetes patch. Nothing about that demo required teaching the model a provider API; it only had to understand one schema, because Crossplane had already flattened "call the cloud provider" down to "watch and patch a custom resource." That's a genuinely different capability than CAPI offers, and it's the honest weight on the "for" side of this comparison — not a hypothetical, a shipped pattern.
The Case Against: What CAPI Already Delivers, Without a Second Framework
Here's the part the "just adopt Crossplane" framing usually skips: for the one thing a self-hosted PaaS actually needs — owned-machine fleet lifecycle — Cluster API already is that single, discoverable, watchable API. It doesn't need Crossplane's genericity to get there, because it was purpose-built for exactly this job.
Watch a Machine today and you get the same agent-operable shape Crossplane's pitch promises — discoverable via the Kubernetes API, inspectable status, watchable for change events — with zero Composition layer in between:
apiVersion: cluster.x-k8s.io/v1beta1
kind: Machine
metadata:
name: worker-fsn1-a3f9
status:
conditions:
- type: Ready
status: "True"
- type: NodeHealthy
status: "True"
nodeRef:
name: worker-fsn1-a3f9An agent doesn't need an XRD to reason about that — kubectl get machine worker-fsn1-a3f9 -o jsonpath='{.status.conditions}' already answers "is this node healthy," and a dead node gets replaced by CAPI's own reconciliation loop, not by a human or an agent invoking a repair action. Wrapping that in a Crossplane XRD would mean standing up a second control-plane framework, deploying and securing a Composition-function runtime, and re-expressing a schema that's already fixed and already stable — for a resource type that isn't the variable part of the problem. Genericity is a cost you pay per resource type you don't yet have; CAPI has no marginal cost here because the resource type (a machine) was never the thing in question.
The concrete failure mode is worth naming, because "just add a layer" reads as free until something breaks. Every Composition function runs as its own pod, called over gRPC during reconciliation — that's a new process an operator has to patch, a new RBAC surface a compromised function could abuse to request infrastructure it shouldn't, and a new place a reconcile loop can silently stall if the function pod itself is unhealthy, independent of whether the underlying Machine is fine. None of that risk buys new capability when the thing being wrapped already has a stable, fixed schema — it's pure attack surface and pure operational burden, added to make an already-solved problem go through an extra hop.
The Decision Rule
Collapse both sections into one rule instead of "it depends":
If the resource is fundamentally a machine or a cluster, use Cluster API — a purpose-built provider (CAPH, Metal3, CAPA) already exists, and wrapping it in an XRD only adds a translation layer with nothing to translate. If the resource is something a tenant wants that isn't a machine — a bucket, a DNS record, a TLS certificate issued as a claim — and no purpose-built Kubernetes-native controller for it already exists, that's the actual case for an XRD: you're minting a new API, and Crossplane's genericity is cheaper than hand-writing a controller.
Run that rule against a git-push PaaS's own stated scope and the answer sharpens further. A platform that deliberately doesn't offer managed databases or a multi-cloud abstraction layer — by design, not by omission — has an intentionally narrow, opinionated API surface, not "let tenants claim arbitrary cloud resources." That's the opposite of the problem Crossplane's genericity is priced for. The honest read: a CAPI-based PaaS's roadmap doesn't need a Crossplane layer to satisfy the AI-operability argument, because the machine-lifecycle API it already runs is the single declarative surface an agent needs — the missing piece isn't genericity, it's making sure every resource the platform already exposes (deploys, domains, secrets) is as watchable and status-rich as a Machine already is.
One idea worth borrowing without the full framework: Crossplane's newer Operation primitive runs a function pipeline once to completion, like a Kubernetes Job rather than a Deployment — useful for one-off tasks (cert rotation, a scheduled migration) that don't fit the reconcile-forever model. A platform can take that shape — a declarative, watchable one-off task object — without adopting Crossplane's Composition engine to get it.
Where This Leaves a Self-Hosted PaaS
The CNCF argument that API-first infrastructure is what makes AI operable is correct — it's just not an argument for Crossplane specifically, it's an argument for any purpose-built, status-rich Kubernetes API, and Cluster API already qualifies for the one job it does. The mistake would be treating "Kubernetes-native" and "needs Crossplane" as synonyms, when a narrower, deeper API can deliver the same agent-operability property with less surface area to secure and operate.
Bex.co is the open-source, AI-native Render alternative — push a git repo, get a running HTTPS service on machines you own, lifecycled by Cluster API and CAPH instead of a hand-rolled fleet of scripts. Star the repo on GitHub or deploy your first app today.