03

TaskRuns & Pods

A Task is a template. A TaskRun is one execution of it. When you create a TaskRun, the controller resolves the Task, builds exactly one Pod for it, and keeps copying what that Pod does into the TaskRun's status until a single condition, Succeeded, becomes True or False. Everything else in this chapter (timeouts, retries, cancellation, debugging, waiting for a node) is a different way of reaching that final condition. Each model below runs the real sequence of states, so you can scrub through it and break it.
A TaskRun doesn't have a phase field. It has a condition of type Succeeded whose status is Unknown while work is in flight and True or False at the end, and a reason that says which path it took. Play each scenario and watch the reason move through the state machine while the YAML updates.
inject:

condition Succeeded: the TaskRun state machine

t = 0.0s
Startedstatus UnknownPendingstatus UnknownExceededNodeResourcesstatus UnknownCouldntGetTaskstatus FalseRunningstatus UnknownSucceededstatus TrueFailedstatus FalseTaskRunTimeoutstatus FalseTaskRunCancelledstatus False
status
Unknown
reason
Started
pod
—
duration
—
completionTime − startTime
kubectl get taskrun build-run -o yaml
status:
conditions:
- type: Succeeded
status: "Unknown"
reason: Started
startTime: "2026-10-07T10:00:00Z"

Try: play the happy path, then pick a scenario or press “make build exit 1” mid-run. Every outcome is the same condition (type Succeeded) with a different status and reason: Unknown while it runs, then True or False. Note ExceededNodeResources keeps status Unknown: the TaskRun waits for capacity instead of failing.

Kubernetes starts all of a Pod's regular containers at once. Tekton makes them run in order by replacing each step's command with its own entrypoint binary, which waits for the previous step's file in /tekton/run before running your command. Init containers prepare that binary and your scripts first. Sidecars start next to the steps and are stopped after the last one.

Pod build-run-pod: one container per step

pod phase: —
0s5s10sprepareplace-scriptssidecar-registrysidecarstep-clonestep 0step-buildstep 1step-teststep 2step-pushstep 3
TaskRun reason
Started
steps terminated
0/4
init containers
2
run once, in order, before any step
sidecar
waiting
stopped by the controller after the last step
the Pod Tekton generates (trimmed)
kind: Pod
metadata:
name: build-run-pod
labels: {tekton.dev/taskRun: build-run}
ownerReferences: [{kind: TaskRun, name: build-run}]
spec:
initContainers:
- name: prepare # entrypoint → /tekton/bin
- name: place-scripts # script: → /tekton/scripts
containers:
- name: step-clone
command: [/tekton/bin/entrypoint]
args: [-wait_file, /tekton/downward/ready,
-post_file, /tekton/run/0/out, ...]
- name: step-build
command: [/tekton/bin/entrypoint]
args: [-wait_file, /tekton/run/0/out,
-post_file, /tekton/run/1/out, ...]
- name: step-test
command: [/tekton/bin/entrypoint]
args: [-wait_file, /tekton/run/1/out,
-post_file, /tekton/run/2/out, ...]
- name: step-push
command: [/tekton/bin/entrypoint]
args: [-wait_file, /tekton/run/2/out,
-post_file, /tekton/run/3/out, ...]
- name: sidecar-registry # starts with steps
TaskRun status.steps[] (mapped from container states)
status:
conditions:
- type: Succeeded
status: "Unknown"
reason: Started
startTime: "2026-10-07T10:00:00Z"

Try: play it and watch each step's entrypoint block on the previous step's /tekton/run/N/out file. Then make step-test exit 1: it writes out.err and push is skipped without running its command. Add onError: continue: the failure is recorded (terminationReason Continued) and push runs.

When a step exits, its entrypoint writes a termination message (step results plus start time) to /tekton/termination. Kubernetes copies it into the container status, which is how results leave the Pod without the controller reaching inside. It is also why results have to be small. The Steps & Tasks chapter goes deeper on the entrypoint, and Params & Results on what results can carry.

Timeouts: 60 minutes unless you say otherwise

taskrun-timeouttaskrun-start-completion
Every TaskRun has a deadline measured from startTime. If you don't set spec.timeout it comes from config-defaults, which ships as 60 minutes. When the clock runs out the controller doesn't wait for a step to finish: it marks the run TaskRunTimeout and deletes the Pod.

TaskRun build-run against its deadline

0m20m40m60mpreparestep-clonestep 0step-buildstep 1step-teststep 2timeout
elapsed
0m
work needs
66m
if nothing stopped it
reason
Started
pod
—
apiVersion: tekton.dev/v1
kind: TaskRun
metadata:
name: build-run
spec:
taskRef: {name: build}
timeout: "1h0m0s" # = the default
---
# ConfigMap config-defaults (tekton-pipelines ns)
data:
default-timeout-minutes: "60"

Try: with the defaults the work needs about 65 minutes and the 60-minute default kills it near the end of the tests. The controller deletes the Pod, so the running step never writes its out file. Raise the timeout, or set it to 0 to turn the deadline off.

Retries: a new Pod per attempt

taskrun-retriestaskrun-retries-status
Set retries on a Pipeline task and a failed TaskRun gets another attempt: its current status is pushed onto status.retriesStatus and a fresh Pod (…-pod-retry1, -retry2) is created. If each attempt fails independently with probability p, the run eventually passes with probability 1 − pʳ⁺¹.

100 PipelineRuns · each square is one attempt (one Pod)

top row = first attempt; red = failed attempt, green = passed. A column with no green is a failed PipelineRun.

P(TaskRun eventually succeeds) = 1 − pʳ⁺¹

70%retries 091%retries 197.3%retries 299.2%retries 399.8%retries 499.9%retries 5
expected success
97.3%
observed
98/100
expected pods / run
1.39
each attempt = a new Pod
observed pods
132
# in the Pipeline
tasks:
- name: test
retries: 2
taskRef: {name: go-test}
---
# TaskRun ci-run-test after attempt 3
status:
conditions:
- {type: Succeeded, status: "True", reason: Succeeded}
podName: ci-run-test-pod-retry2
retriesStatus:
- conditions: [{status: "False", reason: Failed}]
podName: ci-run-test-pod
- conditions: [{status: "False", reason: Failed}]
podName: ci-run-test-pod-retry1

Try: at 30% flakiness one retry turns 70% green into 91%, two into 97%. Now drag P to 100% (a real bug): retries only multiply the Pods you pay for. Each attempt keeps the same TaskRun; the earlier attempts move into status.retriesStatus.

To stop a run you change its spec: set spec.status to TaskRunCancelled (tkn taskrun cancel does exactly this). To investigate a failure, the alpha debug.breakpoints.onFailure option keeps the failing step's container alive so you can exec into it. Logs are per container: one stream per step.

build-run-pod

t = 0.0s
0s5s10s15s20spreparestep-clonestep 0step-buildstep 1step-teststep 2step-pushstep 3
status
Unknown
reason
Started
pod
—
kind: TaskRun
metadata: {name: build-run}
spec:
taskRef: {name: build}
debug:
breakpoints:
onFailure: "enabled"

$ tkn taskrun logs -f build-run

(nothing yet)

Try: play to the failure with the breakpoint on. The TaskRun stays Running and the Pod stays alive, so you can exec in. Run debug-continue and push still runs. Turn the breakpoint off and the failure ends the run at once. Cancel mid-step and the Pod and its logs are deleted.

Tekton has no build queue. The Pod goes to the regular kube-scheduler, which places it on a node with enough unrequested CPU and memory that also matches its nodeSelector and tolerations. If nothing fits, the Pod stays Pending and the TaskRun reports ExceededNodeResources with status Unknown. It is still waiting, and the timeout is still running.

node-a

pool=general
0.0/4 CPU requested

node-b

pool=general
0.0/4 CPU requested

node-c

pool=ci
0.0/4 CPU requested
taint dedicated=ci:NoSchedule
✕ taint dedicated=ci:NoSchedule not tolerated

Pending Pods · 0

t = 0.0s
none, every Pod found a node
running
0
pending
0
avg wait
0.0s
created → scheduled
demand vs supply
5.0/8
CPU wanted ÷ CPU on eligible nodes
kind: TaskRun
spec:
taskRef: {name: build}
podTemplate: {}
---
# in Task build
steps:
- name: build
computeResources:
requests: {cpu: "1"}

Try: start TaskRuns, then raise the CPU request to 2.5: only one Pod fits per node and a queue forms. Turn on the nodeSelector alone: the only matching node is tainted, so nothing schedules until you add the toleration.

podTemplate is how a run picks its nodes (and sets securityContext, volumes, DNS…) without editing the Task. Which ServiceAccount the Pod runs as, through serviceAccountName, decides its credentials. That is covered in Auth & Pod config.
A TaskRun either points at a Task (taskRef, by name or through a resolver) or carries the Task inline (taskSpec). With a reference, the controller fetches the spec once at the start and stores a copy in status.taskSpec. Changes to the Task afterwards only affect new runs.
the TaskRun you create
apiVersion: tekton.dev/v1
kind: TaskRun
metadata:
generateName: build-run-
spec:
taskRef:
name: build # looked up at start
params:
- {name: package, value: ./cmd/server}

Task build in the cluster

kind: Task
metadata:
name: build
generation: 2
spec:
steps:
- name: build
image: golang:1.22

With taskRef, each new TaskRun resolves this object when it starts.

TaskRuns · status.taskSpec snapshot

t = 0s
  • Create a TaskRun.
TaskRuns
0
CouldntGetTask
0
running an older image
0
snapshot taken at start
Task generation
2

Try: create a TaskRun, apply a new Task image while it runs, then create another one. The first keeps the spec it copied into status.taskSpec. Now delete the Task and create a third: CouldntGetTask. Switch to inline taskSpec and the Task object no longer matters.

  • takeawayA TaskRun's state is one condition, Succeeded: Unknown → True/False, plus a reason. Timeouts, cancellations, missing Tasks and failing steps are all just different reasons for False.
  • takeawayOne TaskRun attempt is one Pod. Init containers prepare the entrypoint, the entrypoint orders the steps through files in /tekton/run, and status.steps[] mirrors the container states.
  • takeawayThe controller enforces the edges: it deletes the Pod on timeout or cancel, makes a new Pod per retry, and keeps a failed container alive when you ask for a breakpoint. The cluster's capacity decides how long a run waits before any of that.

Key concepts · 19

book chapter 3
  1. TaskRun

    A namespaced object (tekton.dev/v1) that asks for one execution of a Task with concrete params and workspaces.

    Tasks are templates; a TaskRun is what actually runs. The TaskRun controller reconciles it into exactly one Pod (per attempt) and writes the result back into its status, so the TaskRun is also the record of what happened.

    kind: TaskRuntkn task start buildkubectl create -f taskrun.yaml
    ↑ see it in the visualization
  2. The Succeeded condition

    status.conditions[type=Succeeded], whose status is Unknown while the run is in progress and True or False once it has finished.

    Every outcome (success, failure, timeout, cancellation) is the same condition with a different status and reason. Tools such as tkn, the Dashboard and the PipelineRun controller only have to read this one field.

    status.conditions[0].statuskubectl wait --for=condition=Succeeded tr/x
    ↑ see it in the visualization
  3. reason and message

    A machine-readable reason on the Succeeded condition (Started, Pending, Running, Succeeded, Failed, TaskRunTimeout, TaskRunCancelled, CouldntGetTask, ExceededNodeResources, ExceededResourceQuota…) plus a human message.

    The status says whether the run is done. The reason says why: a timeout and a failing test are both False but need different fixes. Some reasons, like ExceededNodeResources, keep the status Unknown because the run is waiting rather than failed.

    reason: TaskRunTimeoutreason: CouldntGetTaskreason: ExceededNodeResources
    ↑ see it in the visualization
  4. status.podName

    The name of the Pod created for the current attempt, normally <taskrun>-pod (or -pod-retryN on retries).

    It connects the Tekton view to the Kubernetes view: kubectl describe pod, events, node placement and container logs all hang off this name.

    status.podNamekubectl describe pod build-run-pod
    ↑ see it in the visualization
  5. startTime and completionTime

    Timestamps set when the controller first starts the TaskRun and when it reaches a final status.

    The timeout is measured from startTime. Time spent waiting for a node or pulling images counts too, so a cluster that is short on capacity can time out runs that never got to execute.

    status.startTimestatus.completionTime
    ↑ see it in the visualization
  6. Init containers (prepare, place-scripts, working-dir-initializer)

    Containers Tekton adds before the steps: prepare copies the entrypoint binary into a shared volume, place-scripts writes step scripts to /tekton/scripts, and working-dir-initializer creates workingDirs.

    They set up the shared /tekton volumes that let ordinary images behave as ordered steps. While they run, the Pod is Pending and the TaskRun reason is Pending.

    prepareplace-scriptsworking-dir-initializer/tekton/bin/entrypoint
    ↑ see it in the visualization
  7. status.steps[]

    One entry per step, copied from that container's state: waiting, running, or terminated with exitCode, plus Tekton's terminationReason.

    terminationReason (Completed, Error, Continued, Skipped, TimeoutExceeded) shows how a step ended, which the raw exit code can't: a skipped step and a failed step both exit 1.

    status.steps[].terminated.exitCodestatus.steps[].terminationReason
    ↑ see it in the visualization
  8. Termination message

    A small file each step's entrypoint writes on exit (/tekton/termination) that Kubernetes copies into the container status.

    This is how step timing and results get from inside the Pod to the controller without any extra network calls. It is also why results must stay small: the message is capped at about 4 KB per container.

    terminationMessagePath: /tekton/terminationresults
    ↑ see it in the visualization
  9. spec.timeout

    The maximum duration of the TaskRun, defaulting to 60 minutes (default-timeout-minutes in config-defaults). 0 disables it.

    When it elapses the controller marks the run False/TaskRunTimeout and deletes the Pod, so nothing hangs forever. Builds that legitimately take longer need an explicit timeout.

    spec.timeout: "1h30m"config-defaults: default-timeout-minutessteps[].timeout
    ↑ see it in the visualization
  10. retries

    How many more times to try a failed run: set as retries on a Pipeline task, which the PipelineRun passes to the TaskRun it creates.

    Each attempt is a new Pod. Retries hide flaky infrastructure or tests, but they make a real bug cost retries+1 runs before it is reported.

    tasks[].retries: 2<taskrun>-pod-retry1
    ↑ see it in the visualization
  11. status.retriesStatus

    A list holding the full status (conditions, podName, steps) of each earlier failed attempt.

    The top-level status only describes the latest attempt. Without retriesStatus you'd lose the evidence of every failure that a later attempt papered over.

    status.retriesStatus[].podName
    ↑ see it in the visualization
  12. Cancellation

    Patching spec.status to TaskRunCancelled (what tkn taskrun cancel does) stops the run.

    Cancellation is declarative like everything else: you change the spec and the controller acts. It marks the run False/TaskRunCancelled and deletes the Pod, taking its logs with it unless you store them elsewhere.

    spec.status: "TaskRunCancelled"tkn taskrun cancel build-run
    ↑ see it in the visualization
  13. debug.breakpoints.onFailure

    An alpha TaskRun option that keeps a failed step's container alive instead of ending the run.

    You can kubectl exec into the exact environment that failed, then run debug-continue or debug-fail-continue from /tekton/debug/scripts to let the run go on. This beats guessing from logs.

    debug.breakpoints.onFailure: "enabled"/tekton/debug/scripts/debug-continue
    ↑ see it in the visualization
  14. Step logs

    Each step is a container, so its stdout/stderr are that container's logs. tkn taskrun logs -f follows them step by step.

    Logs live with the Pod. Once the Pod is gone (after cancel, timeout or cluster cleanup) they are gone too, unless a log store such as Tekton Results keeps them.

    tkn taskrun logs -f build-runkubectl logs build-run-pod -c step-test
    ↑ see it in the visualization
  15. Pod scheduling and ExceededNodeResources

    The TaskRun Pod goes through the normal kube-scheduler. If no node has room for its resource requests, the run stays Unknown with reason ExceededNodeResources.

    Tekton doesn't queue work itself; the cluster is the queue. Pending time counts against the timeout, so capacity problems can surface as timeouts.

    reason: ExceededNodeResourcesreason: ExceededResourceQuotasteps[].computeResources
    ↑ see it in the visualization
  16. podTemplate

    A TaskRun (or PipelineRun) field for Pod-level settings: nodeSelector, tolerations, affinity, securityContext, volumes and more.

    It lets the same Task run on special nodes (ARM, GPUs, a dedicated CI pool) without changing the Task. A nodeSelector that only matches tainted nodes, without a toleration, leaves the Pod unschedulable.

    spec.podTemplate.nodeSelectorspec.podTemplate.tolerationsconfig-defaults: default-pod-template
    ↑ see it in the visualization
  17. taskRef

    A reference to a Task by name in the namespace, or through a resolver (git, bundles, hub, cluster).

    It lets many runs share one reviewed definition. The spec is resolved when the run starts and copied into status.taskSpec, so editing the Task later doesn't change runs already in flight. A missing Task fails the run with CouldntGetTask.

    spec.taskRef.namespec.taskRef.resolver: gitstatus.taskSpec
    ↑ see it in the visualization
  18. taskSpec (inline)

    The whole Task definition embedded directly in the TaskRun.

    Good for one-off or generated runs: nothing to install first and nothing to drift. The cost is no reuse, and nothing a reviewer can approve separately from the run.

    spec.taskSpec.steps
    ↑ see it in the visualization
  19. serviceAccountName

    The Kubernetes ServiceAccount the TaskRun Pod runs as. It decides which credentials and permissions the steps get.

    It is the identity of the build. Its Secrets are what Tekton's credential initialization turns into git and docker config. The Auth chapter covers it in detail.

    spec.serviceAccountName: ci-bot