Skip to main content

Tekton Joins the CNCF: What Building a Git-Push PaaS's Pipeline Directly on Kubernetes CRDs Actually Buys You

8 min readDora NodaDora Noda
Share
On this page

On March 24, 2026, the CNCF's Technical Oversight Committee voted to accept Tekton as an incubating project, moving it out of the Continuous Delivery Foundation and into the same governance home as Kubernetes itself. That's a milestone for the project, but the more interesting question for anyone building a self-hosted platform is architectural, not political: Tekton's whole pitch is that a CI/CD pipeline can be nothing more than Kubernetes objects — no external CI server, no agent pool, no separate scheduler to run alongside the cluster. A Task is a Pod. A Pipeline is a DAG of Tasks. What does building a git-push PaaS's build-and-deploy pipeline directly on top of that actually look like, and where does it stop being enough on its own?

A Task Is a Pod, and That Changes the Ownership Question

Most CI/CD tools bolt an execution model onto Kubernetes: Jenkins runs agents in pods, GitHub Actions' self-hosted runners register with a control plane that lives outside the cluster, GitLab Runner polls an external API for jobs. Tekton doesn't bolt anything on — it defines the pipeline itself as a set of Kubernetes Custom Resource Definitions, and the Kubernetes API server is the only control plane involved.

The object model is small enough to hold in your head:

  • A Task defines an ordered list of Steps — each Step is a container. Running a Task creates a Pod, and each Step runs as a container in that Pod, in sequence, sharing the Pod's volumes.
  • A Pipeline wires Tasks together into a DAG, declaring which Tasks depend on which, and how results and workspaces flow between them.
  • A TaskRun and PipelineRun are the execution records — create one, and the Tekton controller (itself just a set of pods watching the API server) reconciles it into running Pods, the same way any Kubernetes controller reconciles a Deployment into running Pods.

For a platform already running Cluster API — where nodes, machine deployments, and every tenant workload are already Kubernetes objects reconciled by controllers watching the API server — this is the part that matters: the build pipeline doesn't introduce a second control plane into the fleet. There's no Jenkins master to patch, no GitHub Actions control-plane dependency to inherit (the kind of outage this list has already tracked GitHub through three times in eight days), no separate agent-registration handshake to secure. A build is a PipelineRun object; if the node it's scheduled on dies, Cluster API replaces the node and Kubernetes reschedules the Pod, exactly like it would for any other workload on the fleet. The build layer inherits the same node lifecycle, the same RBAC, the same audit log as everything else the platform already operates.

Turning a git push Into a PipelineRun

CRDs alone don't explain how a webhook becomes a running build — that's Tekton Triggers, a separate but co-developed project with its own small object model:

  • An EventListener runs as a long-lived Deployment with a Service in front of it, exposing an HTTP endpoint a Git host's webhook can hit directly.
  • A TriggerBinding extracts fields from the incoming JSON payload — commit SHA, branch, repo URL, pusher identity — into named parameters.
  • A TriggerTemplate takes those parameters and stamps out a PipelineRun object from them.
  • An optional Interceptor sits in front of the binding to validate a webhook signature or filter on branch name before anything gets created.

Chain those together and git push triggers a webhook, the webhook hits the EventListener's Service, the Interceptor verifies it came from the right repo, the TriggerBinding pulls out the commit SHA, and the TriggerTemplate creates a PipelineRun — all as plain Kubernetes objects the platform's own tooling can list, watch, and audit with kubectl or any controller that wants to. This is the literal mechanism behind "no external CI server": the thing listening for the push event, and the thing running the build once it fires, are both just workloads on the same cluster the platform already manages.

The Build Step Doesn't Have to Be Hand-Rolled

The gap a lot of "build it on Tekton" pitches leave open is: fine, but who writes the container that actually turns a repo into an image? Tekton has no opinion on that by design — a Task's Steps just run whatever containers you point them at. The useful part is that the Cloud Native Buildpacks (CNB) project has already done this work and published it as a Tekton Task: buildpacks-phases in the official tektoncd/catalog.

It doesn't shell out to the pack CLI — it calls the CNB lifecycle binaries directly, one per Step, matching the actual CNB build phases: prepare, detect, analyze, restore, build (or extend, for buildpacks that need to modify the base image), then export. Each phase is its own container in the Task's Pod, sharing a workspace volume that holds the app source and the buildpack layer cache between Steps.

That means a git-push PaaS that wants buildpack auto-detection — the same "no Dockerfile required" convention this list has already covered as increasingly the default for new web apps — doesn't have to reimplement the CNB lifecycle as a bespoke controller. It can wire the CNB project's own Task into its Pipeline as the build stage, and get the detection, layer-caching, and base-image-patching behavior that Cloud Native Buildpacks already maintains, without owning that logic itself. A Dockerfile-based build stage is a straight swap: a Task with a single Step calling buildah bud (or kaniko, or BuildKit) instead of the CNB lifecycle binaries, wired into the same Pipeline as an alternate stage the platform's own detection logic picks between.

What Tekton Deliberately Doesn't Do

Here's the part the "Tasks and Pipelines as CRDs" pitch glosses over: Tekton Pipelines is a CI primitive, not a CD one, and the project doesn't pretend otherwise. It builds and tests; it has no built-in concept of "watch this Git repo and keep the cluster's running state in sync with it." That's why Jenkins X — one of the "higher-level tools" that sits on top of Tekton — pairs it with its own GitOps environment-promotion logic rather than treating Tekton as the whole system, and why the most common pattern in the wild is Tekton for CI paired with Argo CD for CD: Tekton builds and pushes the image, Argo CD watches a Git repo of manifests and reconciles the cluster to match.

A Cluster-API-based platform doesn't need that second half bolted on, because it already has the piece Argo CD would otherwise provide: a controller reconciling a tenant's declared app state (an App custom resource, in bex's case) against what's actually running. So the natural shape is a Pipeline whose final Task doesn't hand off to a separate GitOps tool at all — it's a Step that calls the platform's own controller directly, updating the tenant's App object's image reference (via the Kubernetes API, the same way kubectl set image would) and letting the platform's existing reconciliation loop take it from there, the same loop already driving every other change to that tenant's running state. Deploy isn't a second system bolted onto the build pipeline; it's one more write to an object a controller the platform already runs is already watching.

The other gap is registry credentials and tenant isolation. Tekton has no concept of "this Task belongs to this tenant" — that's ordinary Kubernetes RBAC and namespace boundaries, which the platform has to design deliberately: a build Pod for tenant A should be able to push to tenant A's image path and nowhere else, using a service account scoped to that namespace, not a platform-wide registry credential every PipelineRun can reach. None of this is exotic, but none of it comes for free from Tekton either — it's the platform's own glue, same as the buildpack-vs-Dockerfile detection step upstream of the Pipeline.

The Honest Cost: Verbosity Doesn't Go Away

None of the above is free of friction, and it's worth saying so plainly rather than letting "CNCF-governed standard" imply otherwise. Tekton's own contributors have documented verbose YAML as the project's most persistent pain point — Workspace declarations in particular require repeating the same name mapping between a Pipeline and every Task that uses it, and pipeline specs for anything non-trivial routinely approach etcd's object-size limits. A Pipeline with several concurrent Tasks, conditional when expressions, and a finally block is close to unreadable as raw YAML; the Tekton Dashboard exists largely because rendering the DAG visually is the only practical way to check that a pipeline does what you think it does.

CNCF incubation changes the governance picture — a wider set of maintainers, a vendor-neutral home, less single-company risk than a project living inside one company's roadmap — but it doesn't touch that authoring experience at all. A platform building its own Pipeline and Task definitions on top of Tekton is still hand-writing (or code-generating) that YAML for every tenant build shape it wants to support. The honest scope of "build directly on Tekton's CRDs" is: real leverage on the execution and trigger layer, real reuse on the buildpack build step via the existing CNB catalog Task, and zero help with the verbosity of defining any of it — that part is still on the platform to abstract away from the tenant, who should never see a Task manifest, only a git push.

Bex.co is the open-source, AI-native Render alternative — push a git repo, get a running HTTPS service on machines you own, with the entire build-and-deploy path running as Kubernetes objects on infrastructure you control end to end. 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