Skip to main content

CNCF's Composable-by-Design Rule: What It Actually Costs to Build a Swappable PaaS

8 min readDora NodaDora Noda
Share
On this page

CNCF spent the first half of 2026 telling platform teams to bolt AI tooling onto whatever they already had. On July 6, it reversed course. "Evolving platform engineering for AI-native workloads" names Composable by Design as one of five pillars of what it calls Platform Engineering 2.0 — and the definition is specific: platform capabilities delivered as modular, independently deployable, API-first building blocks, so a team can swap one CNCF-compliant tool for another with equivalent functionality "without cascading changes."

That's a real constraint, not a slogan. It means the answer to "should our platform be composable" isn't yes or no — it's a cost you pay on day one against a payoff you collect on the day someone asks for a capability you didn't design for. This post works that tradeoff through two concrete cases against bex's own architecture: a self-hosted PaaS built on Cluster API, so the question isn't academic — it's the design bex has to make right now, for real, on the seam CNCF's own framework says is coming next: compute provisioning for AI-agent workloads.

What CNCF Actually Said

Platform Engineering 2.0's five pillars are AI-Native Platform, Multi-Persona Experience, Embedded FinOps, Security Shifts Down, and Composable by Design. The AI-Native pillar is the one worth reading closely if you operate infrastructure rather than write about it: it calls out GPU/TPU allocation, model serving, MCP gateways, and agentic guardrails by name as the new capability surface a platform has to support, alongside the human-developer workflows it already handles.

Composable by Design is the pillar that determines whether a platform can pick those capabilities up as they mature, instead of shipping all four on day one or missing the window entirely. bex already treats two of them as separable seams — an MCP gateway sits in front of tenant deploy operations as its own component, and agent-facing guardrails live at the API boundary rather than baked into a monolithic control loop. Those aren't this post's subject. The seam that isn't solved yet, and the one CNCF's own list flags as the most likely to get an unplanned request first, is compute provisioning: what happens the day a tenant's AI agent needs a GPU.

CNCF isn't just theorizing about composability, either. Two weeks after the pillar post, a follow-up on "platform engineering for the agentic enterprise" pointed at OpenChoreo — a CNCF Sandbox project — as a working example of the same idea: a platform for managing applications, resources, and AI agents that stays grounded in ordinary Kubernetes and cloud-native primitives rather than inventing a bespoke control plane. The point isn't that OpenChoreo is the answer for every self-hosted PaaS; it's that "composable enough to add AI-agent capabilities without a rewrite" is a design CNCF is actively validating in public, not a hypothetical this post is inventing to fit bex's roadmap.

Case 1: A Tenant's Agent Needs a GPU

Start from where a Cluster-API-based PaaS actually sits today. A management cluster runs Cluster API with a provider like CAPH (Hetzner) or CAPD (Docker, for local dev) reconciling Cluster and MachineDeployment objects into real node pools. Tenant workloads land on those node pools through a Render-compatible deploy API — push a git repo, get a running HTTPS service. None of that assumes GPUs exist. It assumes compute is CPU cores and RAM, because until an AI-agent workload shows up wanting one, that assumption has been true.

Now a tenant asks to run a GPU-backed inference workload for their agent. Two ways the platform could have been built determine how expensive that request is.

If resource provisioning was built as a swappable primitive — a ResourceProvisioner interface that node-pool implementations register against, with the scheduler and deploy API talking only to the interface, never to a specific provider — then adding GPU support means writing one new implementation. Kubernetes' Dynamic Resource Allocation (DRA) exists specifically to model GPUs, TPUs, and other specialized devices as schedulable resources without hardcoding them into the core scheduler; it graduated to general availability in Kubernetes 1.34, which means the upstream primitive a composable provisioner would delegate to is no longer beta-quality plumbing a self-hosted fleet has to justify running in production. A DRA-backed provisioner slots in as a second implementation of the same interface the CPU/RAM provisioner already satisfies. The deploy API's request schema doesn't change. The billing pipeline doesn't change. The scheduler's contract doesn't change. What changes is one new module, plus the tests that prove it honors the same interface the first implementation did.

If resource provisioning was never abstracted — if the scheduler's bin-packing math assumes every unit of compute is CPU cores and megabytes, if the deploy API's request schema has no field for a device class, if the billing pipeline meters usage in CPU-seconds because that's the only unit that ever needed metering — then a GPU request doesn't add a module. It forces a change in every subsystem that baked in the CPU/RAM assumption: the scheduler has to learn a second resource dimension it wasn't written to reason about, the API schema change is a breaking one that every existing tenant integration has to migrate against, and the billing pipeline needs a new metering unit threaded through reporting and invoicing it wasn't built to carry.

That's the actual shape of the tradeoff, and it's worth being honest about what it costs on both sides. The composable version isn't free on day one: it means writing an interface and a second implementation — the "boring" CPU/RAM one — before there's any GPU workload to justify it, roughly doubling the code in that one layer to prove the abstraction actually holds and isn't just a wrapper around a single hardcoded case. The monolithic version pays nothing on day one. It pays later, when the GPU request lands, and it pays across three subsystems that were never designed to be touched independently — plus a breaking API migration for every tenant already integrated against the old schema. The composable tax is small, paid once, and contained to one module. The monolithic tax is deferred, uncertain in timing, and paid across the whole stack at once.

Case 2: The Same Pattern at Smaller Scale

One example proves a pattern exists in one place. Here's a second, deliberately smaller case, to check whether the tradeoff holds when the capability gap is minor instead of dramatic: build-artifact caching.

A git-push deploy pipeline needs somewhere to cache build layers between deploys. The obvious first choice is local disk on the build node — simplest thing that works, no external dependency. Then a tenant's build outgrows single-node caching, or the platform wants to run build workers across multiple machines that need to share a cache, and local disk stops being enough. An S3-compatible object store is the natural next step.

If the build pipeline talks to an ArtifactStore interface rather than to a filesystem path, adding S3-compatible storage is a second implementation behind the same interface — the pipeline code that reads and writes cache entries doesn't change, only which implementation is wired in. If the build pipeline was written against os.WriteFile and a hardcoded local path because that was the only requirement at the time, adding S3 support means going back into the pipeline itself and changing how every caller writes and reads cache entries.

The magnitude is different — a build cache backend is a smaller, lower-stakes swap than a scheduler's resource model — but the shape isn't. Composable pays a small, contained tax up front regardless of how big the eventual capability gap turns out to be. Monolithic pays nothing until the gap shows up, then pays in proportion to how deeply the original assumption was threaded through the code, which has nothing to do with how big or small the new capability actually is. A one-line config capability can still force a full pipeline rewrite if the original code never separated "what backend" from "how it's used."

When Composability Is Just Overhead

None of this means every seam deserves an interface. Building swappability into a seam nobody will ever actually swap is pure day-one tax with no payoff — it's the failure mode CNCF's own "Composable by Design" framing implicitly warns against when it specifies modular, independently deployable building blocks rather than everything abstracted just in case. A platform that wraps its logging backend, its config file format, and its internal job queue in swappable interfaces before any of them has a second real implementation waiting isn't practicing platform engineering; it's paying the Case 1 tax on seams that will never collect the Case 1 payoff.

The decision rule that falls out of both cases above is narrower than "make everything composable": pay the tax at the seams CNCF's own AI-native pillar already names as near-certain future asks — compute provisioning, model serving, artifact and model storage — because those are exactly the capabilities the framework predicts every AI-native platform will need to add incrementally, not the ones a team is merely being cautious about. Everywhere else, the honest move is to ship the hardcoded version, notice when a second real implementation actually shows up, and abstract at that point instead of before it. A platform that's composable everywhere is no more disciplined than one that's monolithic everywhere; both skip the actual judgment call CNCF is asking platform teams to make.


Bex.co is the open-source, AI-native Render alternative — push a git repo, get a running HTTPS service on machines you own. Star the repo on GitHub or deploy your first app today.

Related articles

Run this on infrastructure you own

bex is the open-source, AI-native Render alternative — push a git repo and get a running HTTPS service on your own machines.

Get started with bex