Tools · Tekton

Tekton

Tekton is a Kubernetes-native CI/CD framework: instead of renting a build server and configuring it in a bespoke YAML dialect, you describe your build as ordinary Kubernetes Custom Resources — a Task is a list of containers, a Pipeline wires Tasks into a graph, and a PipelineRun executes it as pods on your own cluster. It solves the platform problem of “our CI system is a snowflake that nobody owns, with its own users, its own RBAC, its own audit log, and cluster-admin credentials pasted into a settings page” — by making the pipeline just another API object your platform already knows how to secure, template, observe and reconcile.

☺ Explain it like I’m 10

Imagine a factory conveyor belt. Each station on the belt does one job: one washes the parts, one glues them, one paints them, one puts them in a box. A Task is one station. A Pipeline is the whole belt — which station goes after which. A PipelineRun is one actual thing travelling down the belt today at 3pm. The clever bit about Tekton is that the belt is built out of the same Lego bricks as everything else in your cluster, so the same rules, locks and cameras that watch your apps also watch your factory. And because each station is its own little box, you can borrow a station someone else already built instead of making your own.

🦫Your host for this topic: Benny the Beaver — the Guild’s builder, the one who lays the delivery rails. Benny’s rule for CI: “a pipeline should be a thing you can read, review and revert, not a website someone clicks. If I can’t kubectl get it, I don’t trust it.”

What Tekton is and the problem it solves

☺ Like you’re 10: Old build machines were one special computer that everyone shared and nobody understood. Tekton turns the build machine into ordinary cluster stuff you can write down.

Almost every platform team inherits a build system before anything else, and it is usually the messiest thing they own: plugins nobody dares upgrade, hand-configured agents, credentials to production in its own database, and a job language that exists nowhere else in the company. It is a second control plane beside Kubernetes, with a second identity model and a second place to lose secrets.

Tekton answers that with one uncompromising idea: express the pipeline in the Kubernetes API itself. Every noun is a Custom Resource, every step a container, every execution a pod. No build agent to patch, because the agent is your cluster. No separate RBAC, because it is Kubernetes RBAC. No separate audit log, because it is the API server’s.

Where it came from

Tekton began as “Knative Build,” was extracted into its own project, and is now governed by the Continuous Delivery Foundation (a Linux Foundation sibling of the CNCF). That lineage matters: Tekton is deliberately a set of building blocks, not a finished product. Red Hat’s OpenShift Pipelines, Jenkins X and several vendor CI offerings are all Tekton underneath with a UI bolted on. Learning Tekton means learning the substrate those products are built from.

The unit of work is a container, not a plugin

The most freeing consequence: a step is just an image plus a command. Lint with the lint image; scan CVEs with the Trivy image; sign with the cosign image. No plugin to install, no plugin version to reconcile with the server version, nothing that breaks on upgrade. If it runs in a container, it runs in Tekton — identically on a laptop’s kind cluster and in production.

◆ Key idea

Tekton’s bet is that CI is just workload orchestration, and Kubernetes is already extremely good at workload orchestration. So don’t build a scheduler, a credential store, an RBAC system and an audit trail for your build system — inherit the ones the cluster already has. Everything else about Tekton follows from that one decision.

Where it fits in a platform

☺ Like you’re 10: Tekton makes the thing (builds and tests your code into an image). Something else puts the thing on the cluster. Don’t let one tool do both jobs.

On the reference architecture, Tekton sits in the delivery plane — specifically the integration half of CI/CD. Its job starts when a developer pushes code and ends when a tested, scanned, signed artifact exists in a registry and the desired version is recorded in Git. It explicitly does not include reaching into a production cluster with kubectl apply.

The clean handoff to GitOps

The most important architectural sentence on this page: Tekton pushes, GitOps pulls. The last task in a well-designed pipeline does not deploy — it commits a new image tag to the config repository, and Argo CD or Flux reconciles it. That boundary keeps cluster credentials out of the build system entirely, exactly as described in GitOps Workflows. A pipeline ending in kubectl apply needs cluster-admin; one ending in git commit needs a Git token. One of those is far less frightening to lose.

🦆 git push webhook EventListener interceptors → binding → template PipelineRun one execution Task: clone pod · steps Task: scan pod · steps Task: build pod · steps runAfter runAfter workspace “shared” — one PVC threaded through every Task Registry checkout:1.4.3 Config repo tag bumped by last Task 🤖 GitOps CD pulls Tekton’s lane ends at the commit — it never holds cluster-admin

The neighbours

Argo Workflows is the closest sibling — also in-cluster and CRD-driven, but a general DAG engine born from data and ML pipelines. Argo CD and Flux are downstream of Tekton, not competitors. Helm and Kustomize render the manifests Tekton commits. Trivy and cosign are steps inside Tekton, and the sub-project Tekton Chains is a controller that observes completed TaskRuns and PipelineRuns and records signed, SLSA-style provenance attestations for what they produced — supply-chain evidence you get without adding a step to anybody’s pipeline.

CNPE domain relevance

Tekton lives in Domain 2 — GitOps & Continuous Delivery (25%), the exam’s largest slice, and it is the tool the blueprint has in mind for the “continuous integration” and “application and infrastructure deployment” competencies. It touches Domain 5 — Security too: the ServiceAccount model, credential injection and supply-chain steps all live here. The lesson that frames it is CI/CD & Progressive Delivery; this page is the reference behind it.

How it works — architecture and CRDs

☺ Like you’re 10: A controller watches for “runs.” When it sees one, it makes pods, watches them finish, and writes down what happened.

Install Tekton Pipelines and you get a tekton-pipelines namespace whose two load-bearing deployments are the controller, which watches TaskRun and PipelineRun objects and turns them into pods, and the webhook, which validates and defaults incoming resources. Recent releases add a couple of small helpers alongside them — a remote-resolvers deployment (installed into its own tekton-pipelines-resolvers namespace) that fetches Task and Pipeline definitions from Git, a cluster or a catalog at run time, and an events controller that emits CloudEvents. Behaviour is tuned entirely through ConfigMaps in that namespace, chiefly config-defaults (default timeout, default ServiceAccount) and feature-flags (which gates alpha fields).

That is the whole control plane — no queue server, no database, no agent pool. Desired state lives in etcd and the pods are the execution: exactly the controller pattern from Platform APIs & Operators.

The core nouns

The vocabulary is small enough to memorise, and you should. Note the symmetry running down it: two definitions (written once, reusable) and two executions (created constantly, disposable), plus one deprecated leftover you will still meet in older clusters.

ResourceAPI groupWhat it isDefinition or execution?
Tasktekton.dev/v1An ordered list of steps, each a container with a command or script. All steps run in one pod, sequentially, sharing volumes.Definition (namespaced)
ClusterTasktekton.dev/v1beta1Cluster-scoped Task, shared by every namespace. Deprecated — it never existed in the v1 API and is on its way out of v1beta1; the replacement is a remote resolver (cluster, git, bundles, hub) named in the taskRef.Definition (cluster-scoped)
Pipelinetekton.dev/v1A graph of Tasks referenced by taskRef, ordered with runAfter (omit it for parallel), plus when conditions and a finally block.Definition
TaskRuntekton.dev/v1One execution of one Task: params, workspace bindings, ServiceAccount, resulting status and pod name.Execution
PipelineRuntekton.dev/v1One execution of a Pipeline; creates a TaskRun per Task. Where workspaces are bound to real storage and the ServiceAccount is set.Execution

Wiring: params, results, workspaces and sidecars

Four wiring mechanisms carry everything through a pipeline, and knowing which to reach for is most of the skill. Params flow values in: declared with a type (string, array, object) and an optional default, referenced as $(params.name). Results flow small values out: a Task declares results, a step writes to the file at $(results.NAME.path), and a later Pipeline task reads $(tasks.<taskName>.results.NAME). Results are for identifiers — a digest, a commit SHA — not payloads; they travel back through the pod’s termination message into the TaskRun status, so the total per TaskRun is capped at a few kilobytes. Anything larger belongs on a workspace.

Workspaces flow files: a shared filesystem so the repo cloned by one Task is visible to the Task that builds it, and the way you mount credentials and caches. Sidecars run alongside the steps for the whole life of the Task and are killed when the last step ends — typically a throwaway database for integration tests, or a local registry:

  sidecars:                          # in a Task's spec, beside `steps:`
    - name: test-db                  # starts before step 1, killed after the last step
      image: postgres:16
      env:
        - name: POSTGRES_PASSWORD
          value: test                # throwaway — this DB lives for one TaskRun
      readinessProbe:
        exec: { command: ["pg_isready", "-U", "postgres"] }

Reusing other people’s Tasks

You should write very few Tasks yourself. The Tekton catalog — historically browsed on Tekton Hub, now catalogued on Artifact Hub — publishes battle-tested Tasks: git-clone (clones into a workspace, emitting the commit SHA as a result), kaniko and buildpacks (build an image with no Docker daemon), trivy-scanner, and dozens more. Install them into a namespace with kubectl apply, or leave them where they are and reference them remotely at run time with a resolver — the mechanism that replaced ClusterTask. A taskRef may name either a local name or a resolver, never both; the built-in resolvers are cluster (another namespace), git (a repo and path), bundles (an OCI image) and hub (a catalog), and each can be switched on or off in the resolvers ConfigMap:

    - name: fetch
      taskRef:
        resolver: hub                # fetch the Task definition at run time
        params:
          - { name: kind,    value: task }
          - { name: name,    value: git-clone }
          - { name: version, value: "0.9" }   # pin it — never float on latest
⚠ Pin your catalog Tasks

A catalog Task is someone else’s container image running with your ServiceAccount. Always pin an exact version, review the image it runs, and mirror the ones you depend on into your own registry for anything that touches credentials. An unpinned git-clone is a supply-chain hole with a friendly name — see Security & Policy for how to enforce this with admission policy.

The resources you will actually write

☺ Like you’re 10: Here are three real files: one station, one belt, and one “go!” button. Read them in that order and the whole thing clicks.

1. A Task with params, workspaces and a result

This builds an image with Kaniko (a userspace image builder — no Docker daemon, no privileged pod) and reports the resulting digest as a result, so downstream Tasks can reference the image immutably by digest rather than by a tag someone can move. Kaniko is the example here because it is the one most catalog samples use; Buildah, BuildKit and Cloud Native Buildpacks fill the same slot, and it is worth checking which of them is actively maintained before you standardise a golden path on one:

apiVersion: tekton.dev/v1
kind: Task
metadata:
  name: build-image
spec:
  description: Build and push a container image, emitting its digest.
  params:
    - name: image                       # e.g. registry.acme.io/checkout:1.4.3
      type: string
    - name: dockerfile
      type: string
      default: Dockerfile               # defaults make a Task reusable
  workspaces:
    - name: source                      # ① DECLARED on the Task
      description: repo checked out by a previous Task
    - name: docker-config               # credentials, mounted as a file
      optional: true
      mountPath: /kaniko/.docker        # kaniko reads config.json from here
  results:
    - name: IMAGE_DIGEST                # small value passed OUT to later Tasks
      description: sha256 digest of the pushed image
  steps:
    - name: build-and-push
      image: gcr.io/kaniko-project/executor:v1.23.2
      args:
        - --context=$(workspaces.source.path)
        - --dockerfile=$(workspaces.source.path)/$(params.dockerfile)
        - --destination=$(params.image)
        - --digest-file=$(results.IMAGE_DIGEST.path)   # write the result here
      computeResources:
        requests: { cpu: 500m, memory: 2Gi }

2. A Pipeline that orders Tasks and passes a result along

The Pipeline is responsible for three things: ordering (runAfter), mapping its workspace names onto each Task’s workspace names, and threading params and results between Tasks. The finally block runs whether the pipeline passed or failed — the right home for notifications and cleanup.

apiVersion: tekton.dev/v1
kind: Pipeline
metadata:
  name: build-and-ship
spec:
  params:
    - { name: repo-url, type: string }
    - { name: image,    type: string }
  workspaces:
    - name: shared                      # ② DECLARED on the Pipeline
    - name: registry-auth
  tasks:
    - name: fetch
      taskRef: { name: git-clone }      # catalog Task
      params:
        - { name: url, value: $(params.repo-url) }
      workspaces:
        - { name: output, workspace: shared }    # …and MAPPED: task name ← pipeline name
    - name: scan
      runAfter: [fetch]                 # ordering; omit runAfter to run in parallel
      taskRef: { name: trivy-scanner }  # a catalog Task: its param and workspace
      workspaces:                       # names are fixed by whoever wrote it
        - { name: manifest-dir, workspace: shared }
    - name: build
      runAfter: [scan]
      taskRef: { name: build-image }
      params:
        - { name: image, value: $(params.image) }
      workspaces:
        - { name: source,        workspace: shared }
        - { name: docker-config, workspace: registry-auth }
    - name: bump-tag
      runAfter: [build]
      taskRef: { name: update-config-repo }   # commits to the GitOps repo — no kubectl
      params:
        - name: digest
          value: $(tasks.build.results.IMAGE_DIGEST)   # result consumed here
      when:                                            # only promote from main
        - { input: "$(params.repo-url)", operator: in, values: ["https://github.com/acme/checkout.git"] }
  finally:
    - name: notify
      taskRef: { name: post-to-slack }   # runs on success AND failure

3. A PipelineRun that binds storage and identity

The PipelineRun is where abstraction meets reality: workspaces get actual volumes and the run gets an identity. Registry push credentials travel two ways, and it is worth being precise about which is which. A kubernetes.io/dockerconfigjson Secret listed under a ServiceAccount’s secrets: is picked up by Tekton’s credential initialisation and merged into the step’s Docker config — no annotation is needed, because the registry hosts are already inside the auths map. The tekton.dev/docker-0 and tekton.dev/git-0 annotations belong on the other Secret types — kubernetes.io/basic-auth and kubernetes.io/ssh-auth — where a bare username and password carry no hint about which host they unlock. Builders such as Kaniko that read a config file from a fixed path are usually happier being handed the same Secret as a workspace instead, which is why the binding below remaps the key to config.json.

apiVersion: v1
kind: Secret
metadata:
  name: registry-creds
type: kubernetes.io/dockerconfigjson    # hosts are inside the blob — no annotation
stringData:
  .dockerconfigjson: '{"auths":{"registry.acme.io":{"auth":"REPLACE_ME"}}}'
---
# The annotation form is for basic-auth, where the host is NOT implied:
#   apiVersion: v1
#   kind: Secret
#   metadata:
#     name: registry-basic
#     annotations:
#       tekton.dev/docker-0: https://registry.acme.io
#   type: kubernetes.io/basic-auth
#   stringData: { username: ci-bot, password: REPLACE_ME }
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: ci-bot
secrets:
  - name: registry-creds              # ← the SA the pods run as gets the push rights
---
apiVersion: tekton.dev/v1
kind: PipelineRun
metadata:
  generateName: build-and-ship-       # generateName, not name — every run is new
spec:
  pipelineRef: { name: build-and-ship }
  taskRunTemplate:
    serviceAccountName: ci-bot        # v1 nests this under taskRunTemplate
  timeouts:
    pipeline: "30m"                   # always set one; the default is 1h
  params:
    - { name: repo-url, value: https://github.com/acme/checkout.git }
    - { name: image,    value: registry.acme.io/checkout:1.4.3 }
  workspaces:
    - name: shared                    # ③ BOUND to real storage — the step people forget
      volumeClaimTemplate:
        spec:
          accessModes: [ReadWriteOnce]
          resources:
            requests: { storage: 1Gi }
    - name: registry-auth
      secret:
        secretName: registry-creds
        items:                        # remap the key kaniko expects to find
          - { key: .dockerconfigjson, path: config.json }
⚠ Never commit that Secret

The Secret above is shown inline so you can see the shape; in a real repo the plaintext must never exist. Use External Secrets Operator, Sealed Secrets or SOPS so only a reference lives in Git — the full argument is in Secrets Management.

4. Triggers — turning a webhook into a PipelineRun

Core Tekton has no notion of “on push.” That is Tekton Triggers, a separate install with its own API group (triggers.tekton.dev). Three CRDs carry the work: an EventListener (materialised as a Deployment plus a Service named el-<name> that receives webhooks), a TriggerBinding (pluck fields out of the JSON body into params), and a TriggerTemplate (the stamp that produces the PipelineRun). A fourth kind, Trigger, lets you name a binding-plus-template pairing once and reference it from several EventListeners instead of inlining it. Interceptors are the fourth stage of the chain rather than a resource you normally author: they sit inside the EventListener and verify signatures, filter by event type and evaluate CEL before anything is created. The built-in ones (github, gitlab, bitbucket, cel) are shipped as ClusterInterceptor objects you merely refer to by name.

apiVersion: triggers.tekton.dev/v1beta1
kind: TriggerBinding
metadata: { name: github-push }
spec:
  params:                                        # pull values out of the webhook body
    - { name: gitrevision, value: $(body.head_commit.id) }
    - { name: gitrepourl,  value: $(body.repository.clone_url) }
---
apiVersion: triggers.tekton.dev/v1beta1
kind: TriggerTemplate
metadata: { name: build-on-push }
spec:
  params:
    - { name: gitrevision }
    - { name: gitrepourl }
  resourcetemplates:                             # what gets CREATED on each event
    - apiVersion: tekton.dev/v1
      kind: PipelineRun
      metadata: { generateName: build-on-push- }
      spec:
        pipelineRef: { name: build-and-ship }
        taskRunTemplate: { serviceAccountName: ci-bot }
        params:
          - { name: repo-url, value: $(tt.params.gitrepourl) }
          - { name: image,    value: "registry.acme.io/checkout:$(tt.params.gitrevision)" }
        workspaces:                              # EVERY workspace the Pipeline
          - name: shared                         # declares must be bound here too
            volumeClaimTemplate:
              spec:
                accessModes: [ReadWriteOnce]
                resources: { requests: { storage: 1Gi } }
          - name: registry-auth
            secret:
              secretName: registry-creds
              items:
                - { key: .dockerconfigjson, path: config.json }
---
apiVersion: triggers.tekton.dev/v1beta1
kind: EventListener
metadata: { name: github-listener }
spec:
  serviceAccountName: tekton-triggers-sa         # needs RBAC to CREATE PipelineRuns
  triggers:
    - name: on-push-to-main
      interceptors:
        - ref: { name: github }                  # verifies the HMAC signature
          params:
            - name: secretRef
              value: { secretName: github-webhook-secret, secretKey: token }
            - name: eventTypes
              value: ["push"]
        - ref: { name: cel }                     # extra filtering
          params:
            - { name: filter, value: "body.ref == 'refs/heads/main'" }
      bindings:
        - ref: github-push
      template:
        ref: build-on-push
🦆 Dot’s-eye view

“I never look at any of this. I push to main, and a few minutes later a bot comments on my PR with a link to the run and a new image tag lands in the config repo. The one thing I did learn is tkn pr logs --last -f — that’s my whole relationship with CI, and honestly it’s better than clicking through a build server’s web UI trying to find the failing stage.”

Day-to-day commands

☺ Like you’re 10: There’s a little command called tkn that saves you typing long kubectl commands. But kubectl still works, because it’s all just cluster objects.

The tkn CLI

tkn is the purpose-built CLI. Its superpower is streaming logs across a whole PipelineRun — following each Task’s pod in order, prefixed by task and step, instead of chasing pods by hand.

# Discover what's installed
tkn task list                              # or: tkn t ls
tkn pipeline list                          # or: tkn p ls
tkn clustertask ls                         # legacy installs only

# Start things (interactive — tkn prompts for missing params/workspaces)
tkn task start build-image \
  --param image=registry.acme.io/checkout:1.4.3 \
  --workspace name=source,claimName=ci-cache \
  --serviceaccount ci-bot --showlog

tkn pipeline start build-and-ship \
  --param repo-url=https://github.com/acme/checkout.git \
  --param image=registry.acme.io/checkout:1.4.3 \
  --workspace name=shared,volumeClaimTemplateFile=pvc.yaml \
  --workspace name=registry-auth,secret=registry-creds \
  --serviceaccount ci-bot --showlog

# Watch and debug
tkn pipelinerun logs --last -f             # follow the most recent run
tkn pr logs build-and-ship-x9k2p -f -a     # -a/--all also shows Tekton's injected init steps
tkn pr describe --last                     # status per task, params, workspaces, timings
tkn pr list --limit 10
tkn taskrun describe build-and-ship-x9k2p-build

# Re-run and clean up
tkn pipeline start build-and-ship --last   # re-run reusing the previous run's values
tkn pr cancel build-and-ship-x9k2p
tkn pr delete --keep 10                    # runs accumulate forever — prune them

Plain kubectl still works

Everything is an API object, so nothing about Tekton requires tkn. This matters enormously for the exam, where you should assume tkn may not be installed:

kubectl get pipelineruns -n ci                       # short name: pr (careful — not PRs!)
kubectl get taskruns,pods -n ci
kubectl describe pipelinerun build-and-ship-x9k2p -n ci

# A failed run: which condition, and why?
kubectl get pipelinerun build-and-ship-x9k2p -n ci \
  -o jsonpath='{.status.conditions[0].message}{"\n"}'

# Logs the hard way — a TaskRun's pod, one container per step
kubectl logs build-and-ship-x9k2p-build-pod -n ci -c step-build-and-push

# The reference you DO have in the exam: the schema is in the cluster
kubectl explain pipelinerun.spec.workspaces --recursive
kubectl explain task.spec.steps

# Is the controller healthy?
kubectl get pods -n tekton-pipelines
kubectl logs deploy/tekton-pipelines-controller -n tekton-pipelines --tail=50

Gotchas and failure modes

☺ Like you’re 10: Almost every Tekton problem is one of three things: you forgot to plug the shared box in somewhere, the robot isn’t allowed to push, or the whole station won’t fit on one machine.

The workspace three-place rule

The number-one Tekton stumble, and the exam knows it. A workspace is wired in three separate places, and the names need not match — which is precisely why people get lost. ① The Task declares the workspace it needs. ② The Pipeline declares its own workspace and maps it onto each Task’s name (name: is the Task’s name for it, workspace: is the Pipeline’s). ③ The PipelineRun binds it to real storage. Miss ③ and the run fails at validation with a missing-binding error; miss ② and a Task silently gets an empty directory and your build “can’t find the Dockerfile.”

Binding typeShape in the runUse it forWatch out
persistentVolumeClaimclaimName: ci-cacheA long-lived cache shared across runs.Concurrent runs collide on a ReadWriteOnce PVC — see Storage & State.
volumeClaimTemplatean inline PVC specThe default choice: a fresh volume per run, deleted with the run.Needs a default StorageClass with dynamic provisioning, or the run hangs Pending.
emptyDiremptyDir: {}Scratch space within a single Task.Does not survive between Tasks — each Task is a different pod. Classic silent failure.
secret / configMapsecretName: / name:Credentials, kubeconfigs, CA bundles, build settings.Read-only. Don’t try to write into them mid-build.
⚠ The three failures that eat an afternoon

1. The ServiceAccount can’t push. The build succeeds, the push returns 401 UNAUTHORIZED, and the error hides in the last step’s logs. Check the Secret is listed under the SA’s secrets:; that the registry host the credentials name matches the push destination exactly — the key inside the auths map for a dockerconfigjson Secret, or the tekton.dev/docker-0 annotation for a basic-auth one; and that the run actually used that SA (tkn pr describe prints it). 2. The EventListener SA can’t create PipelineRuns. The webhook returns 202, nothing looks wrong, and no run appears — the Triggers ServiceAccount is missing its RoleBinding. Read the el-<name> pod’s logs, not the pipeline’s. 3. Every step is a container in one pod. A Task needing more memory than any node can offer sits Pending forever with no obvious error; kubectl describe pod and read the scheduler events, then split the Task or shrink the requests. See Triage: Workloads.

Runs accumulate, and other slow leaks

PipelineRuns are not garbage-collected by default. A busy team generates thousands, each with TaskRuns and pod records, and eventually etcd and the dashboard both suffer. Set a pruner (Tekton’s operator installs one; otherwise a CronJob running tkn pr delete --keep), and if you need durable history install Tekton Results, which archives completed runs to external storage so cluster copies can be deleted safely. Related: every volumeClaimTemplate workspace provisions a PVC per run — cheap individually, a real line on the FinOps report in aggregate.

Debugging a stuck run

Work outside-in. Is the PipelineRun’s status.conditions message a validation error? Then it never started — a wiring problem: workspaces, params or a missing taskRef. Did the TaskRun start but the pod stay Pending? Scheduling or storage. Did a step exit non-zero? Your build. Tekton also has an alpha debug mode on a TaskRun — spec.debug.breakpoints, which pauses a failed step so you can kubectl exec into the still-running pod and poke around before it is torn down. Like all alpha fields it only works once enable-api-fields is set to alpha in the feature-flags ConfigMap, so confirm that before you reach for it in anger. The general method is in the Troubleshooting Playbook, the delivery-specific version in Triage: Delivery.

Alternatives and when to choose it

☺ Like you’re 10: There are other conveyor belts. Some are easier to start with; Tekton is the one made of the same bricks as your cluster.

The field, side by side

OptionShapeStrengthsWeaknessesChoose it when…
TektonCRDs; every step a container in a podOne RBAC model, one audit log, reusable catalog Tasks, no agents to patchVerbose YAML, no real built-in UI, you assemble the platform yourselfYou are building a platform and want CI to be an API your golden paths can generate.
Argo WorkflowsCRDs; a general DAG engineExcellent fan-out/fan-in, artifact passing, good UILess CI-shaped; fewer off-the-shelf CI tasksYour “pipeline” is really a computation graph — see Argo Workflows.
GitHub Actions / GitLab CIHosted runners outside the clusterHuge ecosystem, zero ops, sits next to the codeSeparate identity and audit surface; tempted into holding cluster credentialsYou want CI working this afternoon — most teams start here, and that is fine.
JenkinsLong-lived server + agentsDoes anything; decades of pluginsPlugin dependency hell, snowflake agents, a second control plane to secureYou have it already and migration cost outweighs the pain.
Argo CD / FluxReconcilers, not buildersContinuous deployment done properlyThey build nothingNever an alternative — always the partner. See GitOps Workflows.

How to actually choose

Three questions settle it quickly. Who writes the pipeline? If the answer is “each team, by hand,” a hosted CI file next to the code wins on ergonomics. If the answer is “the platform generates it from a template,” you want the pipeline to be an API object, and that is Tekton. Where do the credentials live? Hosted runners outside the cluster push you toward long-lived cluster credentials stored in a CI settings page — solvable with workload identity and a GitOps handoff, but a decision you have to make deliberately. What already watches your cluster? If policy, RBAC, cost attribution and metrics are all Kubernetes-shaped, a Kubernetes-shaped build system inherits all four for free; if they are not, Tekton is buying you uniformity you cannot yet spend.

The honest failure mode of choosing Tekton too early is a platform team that has built a worse GitHub Actions and now maintains it. The honest failure mode of choosing it too late is fifty repositories of hand-rolled YAML in a dialect your golden paths cannot generate or lint.

◆ Key idea

Tekton’s honest trade is verbosity now for uniformity later. A hosted CI file is shorter and gets you moving faster; Tekton gives you a pipeline that a platform API can generate, that Kyverno can validate, that Prometheus already scrapes, and that behaves identically on every cluster you own. If your team is three people, take the hosted runner. If you are handing golden paths to fifty teams, the uniformity is the whole point.

🦫 Benny’s workshop · 25 min

On a kind cluster: kubectl apply -f the Tekton Pipelines release, then write a two-Task pipeline by hand — a catalog git-clone followed by a Task whose single step runs ls -la $(workspaces.source.path). Start it with tkn pipeline start --showlog and confirm the second Task sees the cloned files. Now break it three times on purpose, because the breakage is the lesson: ① remove the workspace binding from the PipelineRun and read the validation error; ② put it back but point the Pipeline’s workspace: mapping at a wrong name; ③ swap the binding to emptyDir: {} and watch the second Task find an empty directory with no error at all. Ten minutes of sabotage teaches the three-place rule better than an hour of reading — and those are exactly the failure signatures you meet under exam pressure.

🎬 At the Platform Guild
🦊

Foxy: Our build server has forty plugins and one person who knows the admin password. That’s… fine, right?

🦫

Benny: It’s a second control plane with its own users and its own secrets. In Tekton the pipeline is a CRD — same RBAC, same audit log, same kubectl get as everything else.

👺

Gizmo: Easy! Give the pipeline cluster-admin and end it with kubectl apply -f. One step! Ship it! 🤑

🐢

Timmy: And now every catalog Task you didn’t read can delete production. Tekton’s lane ends at the commit, Gizmo. The reconciler does the applying.

🤖

Recon: BEEP. Commit the tag. I will notice within a minute and reconcile it. I do not require your credentials.

🦆

Dot: My build says “can’t find the Dockerfile” but I definitely cloned the repo?

🦫

Benny: Three places, Dot. Declared on the Task, declared and mapped on the Pipeline, bound in the PipelineRun. You’ve got two out of three — and two out of three fails quietly.

Exam relevance and going further

☺ Like you’re 10: On exam day you can’t open Tekton’s website. So the shapes have to already be in your head.

What to be able to do cold

Tekton is on the official CNPE tool list, inside the exam’s biggest domain. From an empty file, be able to write: a Task with params, workspaces and steps; a Pipeline referencing two Tasks with runAfter and mapping a workspace to each; a PipelineRun binding that workspace to a PVC or volumeClaimTemplate and setting a ServiceAccount. Be able to say in one breath what a TaskRun is versus a Task, name the three places a workspace is wired, explain why a step needs no Docker daemon, and describe how EventListener, TriggerBinding and TriggerTemplate turn a push into a run. And be able to read a failing run outside-in: PipelineRun, then TaskRun, then pod, then step container.

⚠ Tekton’s docs are NOT available during the exam

During the CNPE the only permitted documentation is https://kubernetes.io/docs (including translations), https://kubernetes.io/blog/, task-specific documentation linked from the exam’s own “Quick Reference” box, and documentation installed locally on the exam machine (man pages, /usr/share, distribution packages). tekton.dev is not on that list. No Task reference, no Triggers guide, no catalog page. Every manifest above has to come out of your memory — so drill the skeletons on Know It Cold, which has the Task → Pipeline → PipelineRun chain stripped to exactly the fields that carry meaning. Your two in-exam lifelines are the Quick Reference box (read it on every task) and kubectl explain against the installed CRDs, which gives you an authoritative, version-correct field list with no browser at all. Practise kubectl explain pipelinerun.spec --recursive until it feels like a reflex.

⚖ CNPA vs CNPE — That allowlist is a CNPE-specific mechanic; CNPA has no allowlist at all, because CNPA is fully closed-book — zero external resources, zero lookups of any kind, on anything. That’s stricter than CNPE, not looser. Even so, the Tekton shapes on this page — Task versus TaskRun, the three-place workspace wiring, where a pipeline’s job ends in a GitOps platform — are worth knowing cold, since this concept-level knowledge still matters for CNPA’s closed-book recall.

Going further

Outside the exam, read the official docs — they are good: Tekton Pipelines (Tasks, then Pipelines, then Workspaces), Tekton Triggers, the tkn CLI reference, the reusable Tasks in the Tekton catalog and on Artifact Hub, and Tekton Chains for signed supply-chain provenance. Then come back here: CI/CD & Progressive Delivery for the lesson this page supports, Release Engineering for versioning and promotion, the tool landscape for how Tekton sits beside the other exam tools, Command Reference for the lines worth muscle-memorising, and the glossary when a noun stops meaning anything.

🐢 Timmy’s checkpoint

1. What is the difference between a Task and a TaskRun? 2. Name the three places a workspace must be wired, and say which one people forget. 3. Where does a Tekton pipeline’s job end in a GitOps platform, and why? 4. How does a step get permission to push to a private registry? 5. Which Triggers resources turn a git push into a PipelineRun, and where do interceptors fit? 6. Why does binding a workspace to emptyDir break a two-Task pipeline? 7. Which documentation may you open during the CNPE — and is tekton.dev among it?

Check your answers
  1. A Task is a reusable definition (a list of steps); a TaskRun is one execution of it, carrying the params, workspace bindings, ServiceAccount and resulting status. Same relationship as Pipeline to PipelineRun.
  2. ① declared on the Task; ② declared on the Pipeline and mapped onto each Task’s workspace name; ③ bound to real storage in the PipelineRun — and ③ is the one people forget, which fails the run at validation.
  3. It ends at a commit — pushing the image to a registry and writing the new tag into the config repo. The GitOps reconciler pulls from there, so the pipeline never needs cluster credentials.
  4. A kubernetes.io/dockerconfigjson Secret listed under the secrets: of the ServiceAccount the run uses (spec.taskRunTemplate.serviceAccountName on a PipelineRun) — that type needs no annotation, since the registry hosts are already keys in its auths map. The tekton.dev/docker-0 annotation is for kubernetes.io/basic-auth Secrets, where the host is not implied. Alternatively bind the Secret as a workspace at the builder’s config path.
  5. EventListener (receives the webhook; runs as a Deployment plus an el- Service), TriggerBinding (extract params from the body) and TriggerTemplate (stamp out the PipelineRun), with an optional Trigger object to package a binding-plus-template pairing for reuse. Interceptors are a stage inside the EventListener, not something you usually author: they verify the signature, filter by event type and evaluate CEL before any run is created.
  6. Because each Task runs in its own pod, and an emptyDir lives and dies with a single pod. The second Task gets an empty directory — usually with no error, just a confusing “file not found.” Use a PVC or volumeClaimTemplate to share files across Tasks.
  7. Only kubernetes.io/docs, kubernetes.io/blog, pages linked from the exam’s Quick Reference box, and locally installed docs. Notekton.dev is not permitted, so use kubectl explain and your memory.