Table of Contents#
- Where Tekton Fits — Building Blocks, Not a Platform
- Tekton's Architecture — CRDs All the Way Down
- Task and TaskRun — the Smallest Unit of Work
- Steps — Why Every Step Is Its Own Container
- Pipeline and PipelineRun — Composing Tasks into a DAG
- A Minimal Pipeline, Built Up Step by Step
- Workspaces — Sharing Data Between Tasks
- Parameters and Results
- StepActions — Reusable Steps Within a Task
finallyTasks — Cleanup That Always Runs- Tekton Results — Long-Term Storage for Pipeline History
- Comparing Tekton to Argo Workflows
- Resolvers — How Tekton Fetches Remote Definitions
- A Worked Example: Kaniko-Based Image Builds Without Privileged Access
- Tekton Hub — Community-Shared Reusable Tasks
- Tekton Triggers — Turning Webhooks into PipelineRuns
- Tekton Chains — Automatic Supply-Chain Attestation
- The Tekton Dashboard and CLI
- Security Model — RBAC and Per-Task ServiceAccounts
- A Full Realistic Multi-Stage Pipeline
- Tekton vs. Every Other Platform — the Build-vs-Buy Framing
- Common Mistakes
- Worked Practice Problems
- Summary and What's Next
Where Tekton Fits — Building Blocks, Not a Platform#
Every platform covered in Parts 4-10 — even self-hosted Jenkins — ships as a complete product: a UI, an opinionated YAML schema, a scheduler, a way to trigger from a git event. Tekton is a different category of thing entirely, and understanding that category difference is the entire point of this chapter. Tekton is a set of Kubernetes Custom Resource Definitions (CRDs) — Task, Pipeline, TaskRun, PipelineRun, and more — that define CI/CD building blocks as native Kubernetes objects, with no bundled UI, no bundled git-event triggering, and no opinion at all about how a pipeline should actually be organized. A platform team assembles a CI/CD system out of Tekton's primitives; it doesn't adopt Tekton as a finished product the way it would adopt CircleCI or GitLab.
Diagram
Why this matters practically, and why an organization would deliberately choose "assemble" over "adopt": every pipeline execution is a genuine Kubernetes object (a TaskRun or PipelineRun), scheduled, scaled, and secured by the exact same Kubernetes primitives (RBAC, ResourceQuotas, NetworkPolicies, the Kubernetes scheduler itself) already governing every other workload in the cluster — this course's Kubernetes deep-dive's entire toolset applies natively and directly, with zero translation layer. A platform team building an internal developer platform (a curated, opinionated layer on top of raw Kubernetes, exposed to product teams as a simplified self-service interface) very commonly chooses Tekton specifically because it's unopinionated building blocks rather than a finished product — the platform team's own opinions and abstractions become the actual product, with Tekton underneath doing the execution.
A useful mental model for the rest of this chapter: every other platform covered in Parts 4-10 answers "how do I write a pipeline" with one opinionated, largely complete answer. Tekton instead answers a different question — "what are the smallest, most composable primitives a CI/CD system could be built from, expressed as native Kubernetes objects" — and leaves "how do I assemble those into something a developer actually uses day to day" as an open question the adopting platform team answers for itself. Every section below names, explicitly, which specific concern a given Tekton piece addresses and which concerns remain genuinely unaddressed until something else is bolted on.
Tekton's Architecture — CRDs All the Way Down#
Every Tekton concept is a genuine Kubernetes Custom Resource, applied with kubectl apply exactly like a Deployment or a Service — there is no separate Tekton-specific API server, database, or control plane outside the Kubernetes API server itself and the Tekton controller (a set of ordinary Kubernetes controllers watching these CRDs and reconciling them, structurally identical to how any Kubernetes operator works).
Diagram
The Task/TaskRun and Pipeline/PipelineRun split is the single most important structural concept to internalize before anything else in this chapter, and it maps directly onto a distinction already familiar from this course's Kubernetes deep-dive: a Task (or Pipeline) is a template — a reusable definition, like a Kubernetes Deployment spec — while a TaskRun (or PipelineRun) is a specific, one-time instantiation of that template, like the actual Pod a Deployment creates. A single Task definition can be — and, via the Tekton Hub covered later, commonly is — reused across many different TaskRuns and many different Pipelines, exactly the way one Kubernetes Deployment spec produces many interchangeable Pod instances.
Task and TaskRun — the Smallest Unit of Work#
A Task defines an ordered sequence of steps, each running in its own container:
apiVersion: tekton.dev/v1 kind: Task metadata: name: npm-build spec: steps: - name: install image: node:20 script: | npm ci - name: build image: node:20 script: | npm run build
Executed by creating a TaskRun referencing it:
apiVersion: tekton.dev/v1 kind: TaskRun metadata: name: npm-build-run-1 spec: taskRef: name: npm-build
kubectl apply -f taskrun.yaml kubectl get taskrun npm-build-run-1 # STATUS: Running, then Succeeded/Failed kubectl logs -l tekton.dev/taskRun=npm-build-run-1
Under the hood, a TaskRun creates a genuine Kubernetes Pod — each step in the Task becomes a container within that Pod, run sequentially (Tekton uses an internal mechanism to enforce step ordering within one Pod, since Kubernetes Pods don't natively guarantee container start ordering). This is worth stating explicitly because it demystifies what's actually happening: there is no separate Tekton execution engine running builds somewhere else — a Tekton build is a Kubernetes Pod, visible and debuggable with the exact same kubectl commands used for any other workload in the cluster.
This also means every scaling property already familiar from this course's Kubernetes deep-dive applies to Tekton executions without modification — the cluster's own autoscaler provisions capacity for a burst of concurrent TaskRuns exactly as it would for a burst of any other workload's Pods, and a ResourceQuota on a namespace bounds how many concurrent builds that namespace's Tasks can consume, all using mechanisms a Kubernetes-experienced team already understands rather than a CI-platform-specific concurrency model.
Steps — Why Every Step Is Its Own Container#
Every prior platform in this series runs a job's steps inside one shared execution context (one container, one VM). Tekton's steps run as separate containers within the same Pod — a structural choice with real, specific consequences worth understanding precisely:
Diagram
Because every step is its own container, each step can use a completely different container image, with no need for one bloated image containing every tool every step might ever need — a scan step can use a purpose-built Trivy image, immediately followed by a build step using a Node image, immediately followed by a push step using a crane or skopeo image, each minimal and purpose-specific. Steps share data via the Pod's local filesystem (an emptyDir volume mounted into every container) for anything transient within a single TaskRun, and via Workspaces (covered next) for anything that needs to persist or be shared across multiple TaskRuns within one PipelineRun.
This per-step image granularity is a genuine security and maintenance win, worth stating plainly: a monolithic "do-everything" CI image accumulates tools and their transitive dependencies over time, each one an addition to the image's overall vulnerability surface even on builds that never actually use that particular tool — Tekton's per-step containers mean a given step's attack surface is bounded by exactly what that one step's image contains, nothing more.
This is architecturally the same idea as the multi-container pod pattern already covered for Jenkins's Kubernetes agents in Part 9 (container('node'), container('docker') inside one dynamically-provisioned pod) — except in Tekton, this multi-container-per-unit-of-work model isn't an optional agent configuration choice, it's the fundamental, only way a Task is structured.
Pipeline and PipelineRun — Composing Tasks into a DAG#
A Pipeline composes multiple Tasks (referenced by name, or defined inline) into a dependency graph, using runAfter to declare ordering — Tekton's equivalent of every prior platform's needs:/requires:/dependsOn:
apiVersion: tekton.dev/v1 kind: Pipeline metadata: name: build-test-deploy spec: tasks: - name: build taskRef: { name: npm-build } - name: unit-test taskRef: { name: npm-test } runAfter: [build] - name: lint taskRef: { name: npm-lint } runAfter: [build] - name: deploy taskRef: { name: deploy-task } runAfter: [unit-test, lint]
Diagram
Tasks with no runAfter relationship to each other run in parallel automatically — unit-test and lint both only depend on build, so they execute concurrently, exactly the same DAG-building behavior already established for every prior platform's dependency-declaration mechanism. A Task can also declare an implicit dependency purely by consuming another Task's Result ($(tasks.build.results.image-digest), covered later in this chapter) — Tekton infers the ordering automatically from that data dependency, without needing a redundant explicit runAfter on top of it. Executed via a PipelineRun:
apiVersion: tekton.dev/v1 kind: PipelineRun metadata: name: build-test-deploy-run-1 spec: pipelineRef: name: build-test-deploy
A PipelineRun is also where Tekton's own timeout controls live — spec.timeouts bounds how long the overall Pipeline, and optionally each individual Task within it, is allowed to run before being forcibly cancelled, the same "don't let a hung step run forever, silently consuming cluster resources" safeguard already assumed by every SaaS platform's own default job timeout in this series, here an explicit field a platform team sets deliberately rather than inheriting from a vendor default.
A Minimal Pipeline, Built Up Step by Step#
Step 1 — the smallest possible Task:
apiVersion: tekton.dev/v1 kind: Task metadata: name: hello spec: steps: - name: say-hello image: busybox script: echo "hello"
Step 2 — a Task that actually checks out source code, using the community git-clone Task from the Hub (covered fully in the next section):
apiVersion: tekton.dev/v1 kind: Pipeline metadata: name: build-pipeline spec: workspaces: - name: source tasks: - name: fetch-source taskRef: { name: git-clone } workspaces: - { name: output, workspace: source } params: - name: url value: https://github.com/my-org/my-repo - name: build taskRef: { name: npm-build } runAfter: [fetch-source] workspaces: - { name: source, workspace: source }
Step 3 — adding parameters so the same Pipeline definition can build different branches/versions:
apiVersion: tekton.dev/v1 kind: Pipeline metadata: name: build-pipeline spec: params: - name: git-revision default: main workspaces: - name: source tasks: - name: fetch-source taskRef: { name: git-clone } workspaces: [{ name: output, workspace: source }] params: - { name: url, value: https://github.com/my-org/my-repo } - { name: revision, value: $(params.git-revision) } - name: build taskRef: { name: npm-build } runAfter: [fetch-source] workspaces: [{ name: source, workspace: source }]
Step 4 — a full PipelineRun supplying a concrete Workspace (a PersistentVolumeClaim) and parameter value:
apiVersion: tekton.dev/v1 kind: PipelineRun metadata: name: build-pipeline-run-1 spec: pipelineRef: { name: build-pipeline } params: - { name: git-revision, value: feature/new-checkout-flow } workspaces: - name: source volumeClaimTemplate: spec: accessModes: [ReadWriteOnce] resources: { requests: { storage: 1Gi } }
Workspaces — Sharing Data Between Tasks#
A Workspace is Tekton's mechanism for sharing data across separate Tasks within one Pipeline (as opposed to the Pod-local emptyDir sharing between steps within one Task, covered earlier) — most commonly backed by a Kubernetes PersistentVolumeClaim, so the source code fetched by one Task is genuinely available to the next Task's entirely separate Pod.
Diagram
This is Tekton's direct equivalent of GitHub's upload-artifact/download-artifact (Part 4) or Azure's PublishPipelineArtifact/DownloadPipelineArtifact (Part 8) — data passed between genuinely separate execution units — but implemented via a real, standard Kubernetes storage primitive (a PVC) rather than a CI-platform-specific artifact store. A Workspace can also be backed by a ConfigMap, a Secret, or an emptyDir (for data that only needs to survive within one PipelineRun, not across separate runs), giving genuine flexibility in exactly what kind of Kubernetes storage backs a given sharing need.
Binding a Secret-backed Workspace to a Task is also how credentials commonly flow into a Tekton build — a Kubernetes Secret, created and RBAC-scoped the same way any other cluster secret would be, mounted read-only into whichever specific TaskRun genuinely needs it, rather than a CI-platform-specific secrets store requiring its own separate access-control model to learn.
Parameters and Results#
Parameters (params) make a Task or Pipeline configurable at run time — already shown in the previous section's git-revision example, directly analogous to every prior platform's pipeline-input mechanism.
Results are Tekton's mechanism for a Task to produce a small output value another Task can consume — the direct equivalent of GitHub's job outputs (Part 4) or GitLab's needs...outputs (Part 6):
apiVersion: tekton.dev/v1 kind: Task metadata: name: build-and-tag spec: results: - name: image-digest description: The digest of the built image steps: - name: build image: gcr.io/kaniko-project/executor script: | # ... build the image ... echo -n "$IMAGE_DIGEST" > $(results.image-digest.path)
Consumed by a later Task in the same Pipeline:
tasks: - name: build taskRef: { name: build-and-tag } - name: deploy taskRef: { name: deploy-task } runAfter: [build] params: - name: digest value: $(tasks.build.results.image-digest)
Writing a Result requires the producing step to write its value to a specific file path ($(results.<name>.path)) rather than setting an environment variable or using a dedicated CLI command the way GitHub's >> $GITHUB_OUTPUT (Part 4) works — a small but genuinely important mechanical detail, since Tekton's controller watches that file's contents after the step completes to populate the Result, and a step that fails to write to the expected path silently produces an empty Result rather than an explicit error.
Results have a size limit (a few kilobytes, by default) precisely because they're designed for small values — an image digest, a version string, a boolean flag — not for passing large build outputs between Tasks, which is exactly the use case Workspaces exist for instead.
StepActions — Reusable Steps Within a Task#
A more recent addition to Tekton worth knowing about, addressing a genuine gap in the original Task-only model: StepActions let a single step be extracted and reused across multiple different Tasks, the way a Tekton Task itself is reused across multiple TaskRuns and Pipelines.
apiVersion: tekton.dev/v1beta1 kind: StepAction metadata: name: npm-install spec: image: node:20 script: npm ci
apiVersion: tekton.dev/v1 kind: Task metadata: name: npm-build spec: steps: - name: install ref: { name: npm-install } # references the StepAction instead of inlining the image+script - name: build image: node:20 script: npm run build
Why this closes a genuine gap the Task-only model left open: before StepActions, if ten different Tasks across an organization all needed the identical "install npm dependencies" step, that step's image:/script: had to be copy-pasted into all ten Task definitions — any improvement (a caching flag, a security fix) required updating all ten separately, the exact kind of duplication-drift problem this series has flagged repeatedly for every other platform's reusability story. StepActions let that one step be defined once and referenced everywhere, at a finer grain than a whole reusable Task, directly closing the gap between Tekton's original two-tier model (Task, Pipeline) and the step-level reuse every other platform in this series (GitHub composite actions, GitLab include, CircleCI orb-provided steps) already had from the start.
finally Tasks — Cleanup That Always Runs#
A Pipeline's finally block is Tekton's direct equivalent of the post { always { } }/post { failure { } } blocks already covered for Jenkins in Part 9, and the if: always()/if: failure() conditions covered for GitHub in Part 4 — a set of Tasks guaranteed to run after every other Task completes, regardless of whether they succeeded or failed.
apiVersion: tekton.dev/v1 kind: Pipeline metadata: name: build-pipeline spec: tasks: - name: build taskRef: { name: npm-build } - name: test taskRef: { name: npm-test } runAfter: [build] finally: - name: cleanup taskRef: { name: cleanup-workspace } - name: notify taskRef: { name: slack-notify } params: - name: status value: $(tasks.test.status) # inspect whether 'test' succeeded or failed
A finally Task can inspect the aggregate and per-task status of everything that ran before it ($(tasks.<name>.status), or $(tasks.status) for the overall Pipeline result) via Tekton's built-in result-status variables, letting a single notify Task branch its own behavior based on what happened — the same "was this a success or failure notification" logic already covered for every prior platform's outcome-conditional mechanism, expressed here as an explicit parameter substitution rather than a named block per outcome.
Tekton Results — Long-Term Storage for Pipeline History#
Worth flagging as a genuinely easy-to-miss operational gap for a team new to Tekton: Kubernetes itself does not retain completed Pods (and therefore completed TaskRun/PipelineRun objects) indefinitely — the cluster's own garbage collection and any configured object-count limits will eventually prune old, completed runs, meaning pipeline history that every SaaS platform in this series keeps automatically (build logs, run history, dashboards going back months) simply disappears from a raw Tekton/Kubernetes setup unless something explicitly archives it first.
Diagram
Tekton Results is the component that closes this gap — a separately installed service that watches for completed TaskRun/PipelineRun objects and archives their full record (spec, logs, results, status) into durable external storage before the live Kubernetes objects are ever garbage collected, then serves that archived history back through its own API (and the Dashboard, when installed) independent of what still exists live in the cluster. This is worth calling out specifically as another concrete instance of this chapter's central theme — a capability every SaaS platform in this series bundles invisibly (indefinite build history) is, in Tekton, an entirely separate, opt-in piece a platform team has to actively choose to install and operate, or accept that pipeline history beyond the cluster's own retention window is simply gone.
Comparing Tekton to Argo Workflows#
A comparison worth addressing directly, since the two are commonly confused or treated as interchangeable: Argo Workflows is another popular, genuinely different Kubernetes-native CRD-based workflow engine, closely related to Argo CD (this course's GitOps chapter, Part 3) by shared project governance but architecturally distinct from Tekton.
| Tekton | Argo Workflows | |
|---|---|---|
| Primary design target | CI/CD specifically (build, test, deploy) | General-purpose workflow orchestration — CI/CD is one use case among many (data pipelines, ML training, batch jobs) |
| Reusability primitive | Task/StepAction, via the Tekton Hub | Templates, via the Argo Workflow Templates concept |
| Ecosystem sibling | Tekton Triggers, Tekton Chains, Tekton Dashboard | Argo CD (GitOps, Part 3), Argo Rollouts (progressive delivery, Part 13), Argo Events |
| CI/CD-specific conventions | Built around Task/Pipeline as CI/CD-shaped concepts from the start | More general-purpose DAG/step model, commonly adapted to CI/CD rather than purpose-built for it |
The practical guidance worth taking from this comparison: an organization already invested in the broader Argo ecosystem (Argo CD for GitOps deployment, per Part 3, and/or Argo Rollouts for progressive delivery, per Part 13) has a real, concrete reason to consider Argo Workflows for CI/CD specifically — shared tooling, shared operational knowledge, shared UI. An organization with no existing Argo investment, evaluating purely on which tool is more purpose-built for CI/CD specifically, generally finds Tekton's narrower, CI/CD-first design (and its larger, more CI/CD-specific Hub ecosystem) a more direct fit — though both are genuinely capable, and this is much more a matter of ecosystem fit than one being categorically superior to the other.
Resolvers — How Tekton Fetches Remote Definitions#
The resolver: hub syntax used earlier (referencing a Tekton Hub Task without a separate install step) is one instance of a more general Tekton mechanism worth understanding on its own terms: a Resolver fetches a Task or Pipeline definition from somewhere other than an already-applied, in-cluster Kubernetes object.
| Resolver | Fetches from |
|---|---|
hub | The Tekton Hub catalog directly, by name and version |
git | A specific file at a specific path/ref in any Git repository |
bundles | An OCI-format bundle pushed to a container registry (Task/Pipeline definitions packaged and versioned like a container image) |
cluster | An already-applied, in-cluster Task/Pipeline object (the implicit default when using plain taskRef: { name: ... }) |
tasks: - name: build taskRef: resolver: git params: - { name: url, value: https://github.com/my-org/shared-tekton-tasks } - { name: revision, value: v2.1.0 } - { name: pathInRepo, value: tasks/npm-build.yaml }
The git and bundles resolvers are worth highlighting as Tekton's closest equivalent to a GitHub reusable workflow referenced from another repository (Part 4) or a GitLab CI/CD Component pulled from the Catalog (Part 6) — a Task definition doesn't have to be pre-applied to every cluster that wants to use it; it can be fetched live, at PipelineRun time, directly from a version-controlled source, pinned to an exact revision exactly like every other cross-repository reusability mechanism covered in this series. The bundles resolver specifically leans on OCI registries (the same infrastructure already used for container images) as a versioned, content-addressable distribution mechanism for Task/Pipeline definitions themselves — a genuinely elegant reuse of existing registry infrastructure that has no direct precedent among the finished platforms covered earlier in this series.
A Worked Example: Kaniko-Based Image Builds Without Privileged Access#
Worth a dedicated, concrete example because it's one of the most common real stumbling blocks for a team's first genuine Tekton pipeline: building a container image from inside a container, without Docker-in-Docker's usual requirement for a privileged container — a meaningful security concern in a shared, multi-tenant Kubernetes cluster, where granting arbitrary Tasks privileged access is a serious blast-radius risk (a privileged container can, with varying difficulty depending on further hardening, escape to the underlying node).
Diagram
apiVersion: tekton.dev/v1 kind: Task metadata: name: kaniko-build spec: params: - name: image workspaces: - name: source steps: - name: build-and-push image: gcr.io/kaniko-project/executor:latest workingDir: $(workspaces.source.path) args: - --dockerfile=Dockerfile - --context=$(workspaces.source.path) - --destination=$(params.image) # NOTE: no privileged: true anywhere in this Task
Why this matters specifically for Tekton, more than it might for a SaaS platform's hosted runners: on a SaaS platform, the vendor's own isolation model (a fresh, single-use VM per job on GitHub-hosted runners, for instance) already substantially contains the blast radius of a privileged container, since that VM is thrown away after one job regardless. On a shared, long-lived, self-managed Kubernetes cluster — Tekton's native and only environment — a privileged Task Pod is running on infrastructure that also hosts other tenants' workloads, production or otherwise, making the "avoid privileged containers entirely" discipline meaningfully higher-stakes than the equivalent choice on a disposable, single-purpose SaaS runner. Kaniko (and similar daemonless builders like Buildah or img) exist specifically to let container image building — inherently one of the most commonly "just give it privileged access" pipeline steps across every platform in this series — fit cleanly into Tekton's least-privilege RBAC model instead of becoming the one Task that has to break it.
Tekton Hub — Community-Shared Reusable Tasks#
Given Tekton ships with zero built-in Tasks of its own (no bundled checkout, no bundled docker-build), a real Tekton pipeline almost always leans heavily on the Tekton Hub (hub.tekton.dev) — a community-maintained catalog of pre-built, reusable Tasks, the ecosystem equivalent of GitHub's Marketplace, GitLab's CI/CD Catalog, or CircleCI's Orb Registry, but built around raw Kubernetes YAML rather than any platform-specific packaging format.
# Install a Task from the Hub directly via the CLI tkn hub install task git-clone tkn hub install task golang-build tkn hub install task trivy-scanner # Or reference it directly by URL in a Pipeline, without a separate install step
tasks: - name: scan taskRef: resolver: hub params: - { name: catalog, value: tekton-catalog-tasks } - { name: type, value: artifact } - { name: kind, value: task } - { name: name, value: trivy-scanner } - { name: version, value: "0.4" }
The same trust/vetting discipline established for every prior platform's reusable-unit ecosystem applies here with equal force, worth restating precisely for Tekton's specific shape: a Hub Task is genuine, arbitrary container-executed code — check its maintenance signal and pin to an exact version (shown above), mirroring the SHA-pinning discipline from Part 5 and the orb-certification-tier discipline from Part 10. Because Tekton has no central platform vendor curating a marketplace the way GitHub or CircleCI does, the Hub's community-maintained, opt-in nature makes this due-diligence step even more the adopting team's own responsibility, not less — there's no platform-level "certified" badge system as authoritative as CircleCI's own registry tiers.
A platform team can also run a private, internal Hub for an organization's own vetted, curated set of Tasks — a genuinely common pattern once Tekton adoption matures past a handful of pipelines, giving internal consumers the same resolver: hub-style ergonomics against a catalog the platform team itself controls and has already vetted, rather than reaching into the public community Hub for every single Task an organization needs.
Tekton Triggers — Turning Webhooks into PipelineRuns#
Tekton core (Task/Pipeline/TaskRun/PipelineRun) has no concept of a git push or a webhook at all — a PipelineRun object has to be created by something, and by default that something is a human running kubectl apply or tkn pipeline start manually. Tekton Triggers is the separate, optional component that closes this gap, turning an incoming webhook (from GitHub, GitLab, Bitbucket, or anywhere else) into an automatically-created PipelineRun.
Diagram
apiVersion: triggers.tekton.dev/v1beta1 kind: TriggerBinding metadata: name: github-push-binding spec: params: - name: git-revision value: $(body.head_commit.id) - name: git-repo-url value: $(body.repository.clone_url) --- apiVersion: triggers.tekton.dev/v1beta1 kind: TriggerTemplate metadata: name: github-push-template spec: params: - name: git-revision - name: git-repo-url resourcetemplates: - apiVersion: tekton.dev/v1 kind: PipelineRun spec: pipelineRef: { name: build-pipeline } params: - { name: git-revision, value: $(tt.params.git-revision) }
This EventListener → Interceptor → TriggerBinding → TriggerTemplate chain is genuinely more moving parts than any other platform in this series requires for the equivalent "run this pipeline when code is pushed" behavior — every SaaS platform covered so far has webhook-to-pipeline triggering built in and invisible; Tekton makes every piece of that chain an explicit, separately-configured Kubernetes resource. This is the clearest single illustration of this chapter's opening framing: Tekton gives a platform team the primitives to build exactly this behavior, rather than shipping it as an assumed default.
The Interceptor stage deserves a specific security callout, since it's the piece most directly analogous to a concern already covered elsewhere in this series: an Interceptor validating the webhook's signature (e.g. verifying a GitHub webhook secret) before any PipelineRun is ever created is the Tekton equivalent of every SaaS platform's built-in webhook-signature verification — skipping it means the EventListener's public endpoint would accept and act on a forged, unauthenticated request from anyone who discovers its URL, a genuinely serious gap given that a successfully forged request results in an actual, real PipelineRun executing with whatever permissions its ServiceAccount carries.
Tekton Chains — Automatic Supply-Chain Attestation#
Tekton Chains is another optional, separately-installed component, worth covering given this series' recurring supply-chain-security thread (Parts 5 and 8, and this course's DevSecOps series) — it automatically observes every completed TaskRun, generates a signed in-toto attestation (the same standard covered in this course's DevSecOps series' supply-chain-security chapter) describing exactly what was built and how, and signs it using cosign, without any explicit signing step written into the Pipeline definition itself.
Diagram
The "automatic, no explicit step required" property is the single most important thing to understand about Chains, and it's a direct consequence of Tekton's architecture from earlier in this chapter: because every TaskRun is already a fully-observable Kubernetes object (its spec, its steps, its resulting image digest all visible to any controller watching the Kubernetes API), Chains can generate provenance for every build across an entire cluster centrally, without each individual Pipeline author needing to remember to add a signing step — directly comparable to the pipeline-decorator concept covered for Azure DevOps in Part 8 (organization-wide, zero-reference-required enforcement), here applied specifically to supply-chain attestation rather than arbitrary injected steps.
The generated attestations follow the exact same in-toto envelope format covered in this course's DevSecOps series' supply-chain-security chapter, and can be verified with the same cosign verify-attestation tooling already covered there and referenced across this series' GitHub, GitLab, and Bitbucket chapters — Tekton Chains is simply one more producer feeding into that same, already-interoperable attestation ecosystem, not a separate, incompatible format of its own.
Chains can be configured to sign with a locally-managed key or, more commonly in a mature setup, via Sigstore's keyless signing (the same OIDC-identity-backed model already covered in this course's DevSecOps series) — eliminating a stored signing key from the cluster entirely, consistent with every other credential-elimination pattern this series has returned to repeatedly.
The Tekton Dashboard and CLI#
Since Tekton ships no bundled UI, two optional, separately-installed pieces close that specific gap:
- Tekton Dashboard — a web UI for visualizing PipelineRuns, their DAG execution graph, and step-by-step logs, deployed as its own set of Kubernetes resources (a Deployment, a Service) into the cluster — genuinely useful, but explicitly optional infrastructure a platform team chooses to run, not a bundled feature.
tkn— the official CLI, for starting PipelineRuns, tailing logs, and inspecting resources without needing rawkubectl:
tkn pipeline start build-pipeline \ --param git-revision=main \ --workspace name=source,claimName=my-pvc \ --showlog # stream logs live as the PipelineRun executes tkn pipelinerun logs build-pipeline-run-1 -f tkn pipelinerun list
Neither piece is required for Tekton to function — every capability covered in this chapter works via raw kubectl apply and kubectl get/kubectl logs alone; the Dashboard and tkn exist purely to make that experience more ergonomic, reinforcing once more that Tekton's actual product surface is the Kubernetes API itself.
For a team building a genuine internal developer platform on top of Tekton, the Dashboard is more commonly treated as a starting point to customize or entirely replace with a bespoke UI, rather than the final product surface handed to end developers — another instance of Tekton providing a usable default rather than a finished, opinionated experience.
Security Model — RBAC and Per-Task ServiceAccounts#
Because every Tekton execution is a genuine Kubernetes Pod, Tekton's entire security model is Kubernetes RBAC — the same ServiceAccounts, Roles, and RoleBindings covered in depth in this course's Kubernetes deep-dive and DevSecOps series, applied here to CI/CD execution specifically, with no separate, CI-platform-specific permission system to learn.
apiVersion: tekton.dev/v1 kind: TaskRun metadata: name: deploy-run spec: taskRef: { name: deploy-task } serviceAccountName: deploy-sa # THIS specific TaskRun runs with THIS specific identity
apiVersion: v1 kind: ServiceAccount metadata: name: deploy-sa --- apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: name: deploy-role rules: - apiGroups: ["apps"] resources: ["deployments"] verbs: ["get", "update", "patch"] # least privilege: exactly what a deploy step needs, nothing more --- apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: deploy-sa-binding subjects: - kind: ServiceAccount name: deploy-sa roleRef: { kind: Role, name: deploy-role, apiGroup: rbac.authorization.k8s.io }
Every TaskRun/PipelineRun can specify its own serviceAccountName, giving genuinely fine-grained, per-execution least privilege — a build TaskRun and a deploy TaskRun within the same Pipeline can run under entirely different ServiceAccounts with entirely different RBAC permissions, directly mirroring the per-job permissions: scoping already established for GitHub Actions in Part 5, expressed here via native Kubernetes RBAC rather than a CI-platform-specific permissions block. This is a genuine security strength worth stating plainly: nothing about Tekton's own design introduces a new privilege-escalation surface distinct from Kubernetes RBAC itself — a team that has already invested in getting Kubernetes RBAC right benefits from that investment directly and immediately for its CI/CD security posture too, rather than needing to separately learn and secure a CI-platform-specific permission model.
The same ServiceAccount used by a deploy TaskRun can itself carry a cloud-provider workload identity binding (an IAM Roles for Service Accounts annotation on AWS, a Workload Identity binding on GCP) — meaning the OIDC-free, direct-workload-identity pattern already covered for runtime workloads in this course's DevSecOps series extends naturally to Tekton's build-time identities too, with no separate cloud-credential mechanism needed at all.
A Full Realistic Multi-Stage Pipeline#
The same build → test → security scan → deploy shape from every prior platform chapter, in Tekton's Task/Pipeline model, referencing a mix of Hub Tasks and custom ones:
apiVersion: tekton.dev/v1 kind: Pipeline metadata: name: full-pipeline spec: params: - name: git-revision default: main workspaces: - name: source tasks: - name: fetch-source taskRef: { resolver: hub, params: [{ name: name, value: git-clone }] } workspaces: [{ name: output, workspace: source }] params: - { name: url, value: https://github.com/my-org/my-repo } - { name: revision, value: $(params.git-revision) } - name: build taskRef: { name: npm-build } runAfter: [fetch-source] workspaces: [{ name: source, workspace: source }] - name: unit-test taskRef: { name: npm-test } runAfter: [build] workspaces: [{ name: source, workspace: source }] - name: security-scan taskRef: { resolver: hub, params: [{ name: name, value: trivy-scanner }] } runAfter: [build] workspaces: [{ name: source, workspace: source }] - name: deploy taskRef: { name: deploy-task } runAfter: [unit-test, security-scan] workspaces: [{ name: source, workspace: source }]
Diagram
Notice this Pipeline definition itself has no concept of "when does this run" or "who approved the deploy" at all — those are, respectively, Tekton Triggers' responsibility (covered earlier) and a separate concern entirely (Tekton has no native manual-approval-gate primitive; a platform team implements one via a custom admission-style controller, an external tool, or simply a manual kubectl apply step gating the deploy PipelineRun's creation) — reinforcing, one final time, that Tekton composes with additional pieces to form a complete system rather than providing every concern natively the way a finished platform does.
Tekton vs. Every Other Platform — the Build-vs-Buy Framing#
The comparison here is structurally different from every prior platform-vs-platform table in this series, because Tekton isn't really a peer to compare feature-for-feature — it's worth framing as a genuine build-vs-buy decision instead:
| A finished platform (GitHub/GitLab/CircleCI/etc.) | Tekton | |
|---|---|---|
| Time to a working pipeline | Fast — write YAML against an opinionated, documented schema | Slower — assemble Tasks, Pipelines, Triggers, Dashboard, RBAC yourself (or via Hub Tasks) |
| Flexibility ceiling | Bounded by the platform's own feature set and extension model | Unbounded — genuine Kubernetes primitives, extensible however the cluster itself is extensible |
| Security model | Platform-specific (tokens, environments, secrets stores) | Native Kubernetes RBAC — no separate model to learn |
| Manual approval gates, notifications, dashboards | Built in | Not built in — assembled from Triggers, Dashboard, and/or external tooling |
| Operational ownership | Vendor-managed (SaaS) or self-hosted controller (Jenkins) | Entirely the adopting team's own Kubernetes cluster |
| Best fit | Most teams, most of the time — the fast, well-supported default | A platform team building a custom internal developer platform, or an organization already deeply Kubernetes-native with specific needs no finished product satisfies |
The honest recommendation, stated as plainly as this series has stated every other platform recommendation: Tekton is the right choice for a genuinely narrow, specific circumstance — a platform team with real Kubernetes operational maturity, building a custom internal developer platform where the finished platforms' assumptions (their specific YAML schema, their specific secrets model, their specific approval-gate shape) are actively the wrong fit for what the organization needs to expose to its own internal product teams. For the large majority of teams — including most teams already running Kubernetes for their production workloads — one of the finished platforms covered in Parts 4-10 remains the faster, lower-total-operational-cost choice; Tekton's power is real, but it is power a team has to actively build the rest of a working CI/CD system around, not power that comes with one already built in.
A middle ground worth naming explicitly, since the choice isn't always strictly binary: several finished platforms/products in the broader ecosystem (not covered as their own chapters in this series) are themselves built on top of Tekton — offering a curated UI, built-in triggering, and an approval-gate model layered over Tekton's raw primitives, effectively selling the "assembly" work as a finished product. An organization drawn to Tekton's native-Kubernetes security model and RBAC integration but unwilling to build the surrounding system itself has this as a genuine third option between "assemble it all from scratch" and "use an entirely non-Kubernetes-native finished platform."
Common Mistakes#
| Mistake | Why it's a problem | Fix |
|---|---|---|
| Expecting Tekton core to trigger automatically off a git push | Tekton core has no webhook/triggering concept at all — that's Tekton Triggers' separate responsibility | Install and configure Tekton Triggers (EventListener/TriggerBinding/TriggerTemplate) explicitly |
Using an emptyDir-style pattern for data that needs to survive across separate Tasks | Steps within one Task share the Pod filesystem automatically; separate Tasks run in separate Pods and do NOT | Use a Workspace (backed by a PVC) for anything that needs to be shared across Tasks |
Referencing an unpinned Hub Task (version: latest or no version at all) | The same mutable-reference supply-chain risk already established for every prior platform's reusable-unit ecosystem | Pin to a specific, exact Hub Task version |
| Assuming a Result was populated correctly without checking | A step that fails to write to $(results.<name>.path) silently produces an empty Result, not an explicit error | Verify the producing step's script genuinely writes the expected value to the expected path |
| Running every TaskRun under one broad, cluster-admin-equivalent ServiceAccount | Violates least privilege — a compromised or misconfigured Task inherits far more access than it needs | Give each TaskRun a scoped ServiceAccount with only the RBAC permissions that specific Task actually needs |
| Choosing Tekton for a small team with no existing Kubernetes operational maturity | The assembly cost (Triggers, Dashboard, RBAC, no built-in approval gates) is real and front-loaded | Default to a finished platform (Parts 4-10) unless the build-vs-buy tradeoff genuinely favors assembly for this specific organization |
| Copy-pasting the same step logic across many Tasks instead of extracting a StepAction | Any fix or improvement has to be applied separately to every duplicated copy, drifting over time | Extract genuinely shared steps into a StepAction, referenced via ref: from every Task that needs it |
| Assuming completed PipelineRuns are retained indefinitely by default | Kubernetes garbage-collects old completed objects — pipeline history quietly disappears without archiving | Install Tekton Results to archive run history to durable storage before objects are pruned |
No finally block for guaranteed cleanup/notification | A failed Pipeline can leave a workspace unclean or nobody notified, with no equivalent of post { always {} } | Add a finally section for any cleanup or notification logic that must run regardless of outcome |
| Reaching for Tekton over Argo Workflows (or vice versa) purely by name recognition | Both are genuinely capable; the better fit depends on existing ecosystem investment, not brand familiarity | Weigh existing Argo CD/Rollouts investment and how CI/CD-specific vs. general-purpose the actual need is |
| An EventListener with no webhook-signature-verifying Interceptor | A forged, unauthenticated request to the public endpoint can trigger a real PipelineRun with real permissions | Always validate the incoming webhook's signature via an Interceptor before any PipelineRun is created |
| Using a privileged Docker-in-Docker step for image builds on a shared cluster | A compromised or misconfigured privileged container has genuine node-escape blast radius, affecting other tenants | Use a daemonless builder (Kaniko, Buildah) that needs no privileged access at all |
Worked Practice Problems#
Problem 1: A platform engineer writes a Tekton Pipeline with two Tasks, build and test, expecting test to automatically see the compiled output build produced, the same way a step within one job would on GitHub Actions. The test Task fails, unable to find the build output at all. What's the most likely cause, and the fix?
Answer: The engineer is very likely relying on Pod-local filesystem sharing (which only works between steps within one Task, since they share one Pod) while build and test are two separate Tasks, each running in its own separate Pod with its own separate, unrelated filesystem — there's no automatic sharing between them at all. The fix: declare a Workspace on the Pipeline, have build write its output there and test read from there, with both Tasks' Workspace bound to the same backing PersistentVolumeClaim in the PipelineRun — this is the mechanism specifically designed for exactly this "share data between separate Tasks" need.
Problem 2: An organization wants every container image built anywhere in its Kubernetes-hosted CI/CD to automatically carry a signed provenance attestation, without relying on individual pipeline authors to remember to add a signing step — comparable to the non-bypassable governance goals already covered for GitLab compliance pipelines (Part 6) and Azure pipeline decorators (Part 8). How does Tekton achieve this, and why does it not require modifying any existing Pipeline definitions?
Answer: Install Tekton Chains, which observes every completed TaskRun across the cluster automatically (since TaskRuns are ordinary, fully-visible Kubernetes objects any controller can watch) and generates + signs an in-toto attestation for each one with zero explicit signing step required in any individual Pipeline's own YAML. This doesn't require modifying existing Pipelines specifically because Chains operates at the Kubernetes-controller layer, entirely outside and independent of any given Pipeline's own definition — the same structural reason Azure's pipeline decorators (Part 8) achieve organization-wide enforcement without touching individual pipeline files, here achieved via Kubernetes's own controller/watch pattern rather than an Azure-DevOps-specific extension mechanism.
Problem 3: A team with no prior Kubernetes operational experience is evaluating Tekton for a new project's CI/CD, primarily because "it's free and open source, unlike our current CircleCI bill." What's the honest assessment of this reasoning, and what should they weigh instead?
Answer: "Free and open source" undercounts the real cost comparison significantly — Tekton itself has no license fee, but the team would be taking on genuine, non-trivial operational responsibility they don't currently have: running and securing a Kubernetes cluster (if they don't already have one for other purposes), configuring Tekton Triggers for webhook-based automatic triggering (built into CircleCI already), standing up and maintaining the Dashboard for pipeline visibility (built into CircleCI's UI already), and building their own manual-approval-gate mechanism (built into CircleCI's type: approval jobs already) — none of which show up as a line-item cost the way a SaaS bill does, but all of which cost real engineering time that has to come from somewhere. The honest framing for this team specifically: unless they already have (or specifically need to build, for other independent reasons) real Kubernetes operational maturity, the CircleCI bill is very likely still the lower total cost once the assembly and ongoing-operational burden of a Tekton-based system is honestly accounted for, not just its absence of a subscription invoice.
Problem 4: A team has ten different Tekton Tasks across their organization, each independently defining an identical "checkout and configure git credentials" first step, copy-pasted into every Task. A security fix needs to be applied to how git credentials are handled. What Tekton mechanism should have been used from the start, and what does the fix look like now?
Answer: A StepAction should have defined that shared step once, referenced by ref: from all ten Tasks — the same problem this chapter's StepActions section describes directly. The immediate fix: extract the shared logic into a single StepAction, apply the security fix there once, then update each of the ten Tasks to reference the StepAction via ref: instead of their own inlined image:/script: — after this migration, any future fix to that shared step only needs to be applied in the one StepAction definition, and instantly applies to every Task referencing it the next time a TaskRun executes.
Problem 5: Six months after adopting Tekton, a platform team is asked by an auditor to show build logs and pass/fail history for a specific production deployment from three months ago. They discover the relevant PipelineRun object no longer exists in the cluster at all. What happened, and what should the team have set up from the start?
Answer: Kubernetes' own garbage collection (or a configured object-count/age limit on completed PipelineRuns) pruned the object months ago — Kubernetes does not retain completed workload objects indefinitely by default, and Tekton inherits that behavior directly since a PipelineRun is a genuine Kubernetes object subject to the same lifecycle rules as any Pod or Job. The team should have installed Tekton Results from the start, which watches for completed runs and archives their full record (spec, logs, status) to durable external storage before the live object is ever pruned — without it, pipeline history beyond whatever the cluster happens to still be retaining live is simply unrecoverable, a genuine compliance and auditability gap every SaaS platform in this series avoids by bundling indefinite build history automatically.
Problem 6: A security review of a shared, multi-tenant Kubernetes cluster running Tekton flags a docker-build Task requiring privileged: true to run Docker-in-Docker, as a serious finding. The team argues it's necessary because "that's how you build container images." Evaluate this argument and propose the fix.
Answer: The argument conflates "necessary for Docker specifically" with "necessary to build a container image at all" — the two aren't the same, and the distinction matters a great deal on a shared cluster where a privileged container's blast radius (potential node-level escape, affecting every other tenant's workloads) is a materially more serious finding than the equivalent risk would be on a disposable, single-tenant SaaS runner. The fix: replace the Docker-in-Docker step with a daemonless builder such as Kaniko, which constructs the image in pure userspace with no Docker daemon and no privileged access required at all — functionally equivalent output (a built, pushed container image) with none of the privileged-access risk, fitting cleanly into the same least-privilege RBAC model the rest of a well-configured Tekton cluster already follows.
Summary and What's Next#
Tekton is a fundamentally different category of tool from every other platform covered in this series: a set of Kubernetes CRDs (Task/TaskRun, Pipeline/PipelineRun) that a platform team assembles a CI/CD system from, rather than a finished product it adopts. Every execution is a genuine Kubernetes Pod, secured by native Kubernetes RBAC (per-TaskRun ServiceAccounts) rather than a CI-platform-specific permission model, and every step within a Task runs as its own separate container, enabling a genuinely minimal, purpose-specific image per step rather than one bloated shared toolchain image. StepActions extend reuse down to the individual-step level; Workspaces (backed by PersistentVolumeClaims) share data across separate Tasks; finally blocks provide guaranteed cleanup/notification regardless of outcome; and Resolvers (hub, git, bundles) fetch Task and Pipeline definitions from remote, version-pinned sources rather than requiring everything pre-applied to the cluster. The Tekton Hub provides community-maintained reusable Tasks with the same pin-and-vet discipline established for every other platform's reusability ecosystem in this series; Tekton Triggers closes the "turn a webhook into a pipeline run" gap every finished platform handles invisibly; Tekton Chains provides automatic, cluster-wide supply-chain attestation with zero per-pipeline configuration required; and Tekton Results closes the "pipeline history isn't retained forever by default" gap that has no equivalent concern on any SaaS platform in this series. The right lens for evaluating Tekton is build-vs-buy, not feature-for-feature — it's the correct choice for a platform team with real Kubernetes maturity building a genuinely custom internal developer platform, and very likely the wrong choice for a team that would otherwise reach for one of the finished platforms covered in Parts 4-10.
Every one of those "gaps" — triggering, dashboards, history retention, approval gates — is worth reading as a checklist, not a complaint: it's the concrete, itemized list of what a team actually takes on when it chooses build over buy, and having it enumerated precisely is what makes that choice an informed one rather than a surprise discovered piecemeal in production.
Part 12 shifts from platform-by-platform coverage to a cross-cutting concern every platform in this series has to answer in its own way: how CI/CD scales to a large monorepo, where testing and building everything on every change quickly becomes prohibitively slow and expensive.
That shift is a deliberate structural change in the series, worth flagging: Parts 4-11 each introduced a new platform's specific mechanics. Parts 12-14 instead take a single cross-cutting problem — monorepo scale, progressive delivery, and self-hosted execution economics respectively — and walk through how each platform already covered answers it, reusing the platform-specific vocabulary this series has now built up rather than introducing yet another platform from scratch.