Tekton Pipelines#

Tekton is a Kubernetes-native CI/CD framework. Every pipeline concept – tasks, runs, triggers – is a Kubernetes Custom Resource. Pipelines execute as pods. There is no central server, no UI-driven configuration, no special runtime. If you know Kubernetes, you know how to operate Tekton.

Core Concepts#

Tekton has four primary resources:

  • Task: A sequence of steps that run in a single pod. Each step is a container.
  • TaskRun: An instantiation of a Task with specific inputs. Creating a TaskRun executes the Task.
  • Pipeline: An ordered collection of Tasks with dependencies, parameter passing, and conditional execution.
  • PipelineRun: An instantiation of a Pipeline. Creating a PipelineRun executes the entire pipeline.

The separation between definition (Task/Pipeline) and execution (TaskRun/PipelineRun) means you define your CI/CD process once and trigger it many times with different inputs.

Tasks#

A Task defines what happens. Each step runs as a container in the same pod, sharing the pod’s volumes and network:

apiVersion: tekton.dev/v1
kind: Task
metadata:
  name: build-go-app
spec:
  params:
    - name: package
      type: string
      description: Go package to build
    - name: output-binary
      type: string
      default: app
  workspaces:
    - name: source
      description: Source code checkout
  results:
    - name: binary-size
      description: Size of the compiled binary in bytes
  steps:
    - name: build
      image: golang:1.22
      workingDir: $(workspaces.source.path)
      script: |
        CGO_ENABLED=0 go build -o $(params.output-binary) $(params.package)
    - name: report-size
      image: alpine:3.19
      workingDir: $(workspaces.source.path)
      script: |
        SIZE=$(stat -c%s $(params.output-binary))
        echo "Binary size: $SIZE bytes"
        echo -n "$SIZE" > $(results.binary-size.path)

Key aspects of this Task:

  • params are typed inputs. They support string, array, and object types. Default values are optional.
  • workspaces are volume mounts shared across steps. The source code lives here.
  • results are small outputs (max 4096 bytes) written to a file path that Tekton provides. Results can be consumed by subsequent Tasks in a Pipeline.
  • steps execute sequentially within the pod. Each step is a container image. The script field replaces command and args when you need multi-line shell logic.

Steps share the pod filesystem. A file written in step 1 is visible in step 2 without any explicit passing mechanism, as long as both steps reference the same workspace or volume.

TaskRuns#

Execute a Task by creating a TaskRun:

apiVersion: tekton.dev/v1
kind: TaskRun
metadata:
  generateName: build-go-app-
spec:
  taskRef:
    name: build-go-app
  params:
    - name: package
      value: ./cmd/server
    - name: output-binary
      value: server
  workspaces:
    - name: source
      persistentVolumeClaim:
        claimName: my-source-pvc

generateName appends a random suffix, so each run gets a unique name. Workspaces bind to concrete storage: PersistentVolumeClaims, ConfigMaps, Secrets, or emptyDir volumes.

You can also embed a Task definition directly in a TaskRun using taskSpec instead of taskRef. This is useful for one-off tasks that do not merit a separate Task resource.

Check results after completion:

tkn taskrun describe build-go-app-abc12
# Or with kubectl:
kubectl get taskrun build-go-app-abc12 -o jsonpath='{.status.results}'

Pipelines#

A Pipeline chains Tasks together with dependency ordering, parameter passing, and result forwarding:

apiVersion: tekton.dev/v1
kind: Pipeline
metadata:
  name: build-test-deploy
spec:
  params:
    - name: repo-url
      type: string
    - name: revision
      type: string
      default: main
    - name: image-name
      type: string
    - name: deploy-namespace
      type: string
      default: staging
  workspaces:
    - name: shared-workspace
    - name: docker-credentials
  tasks:
    - name: fetch-source
      taskRef:
        name: git-clone
      params:
        - name: url
          value: $(params.repo-url)
        - name: revision
          value: $(params.revision)
      workspaces:
        - name: output
          workspace: shared-workspace

    - name: run-tests
      taskRef:
        name: golang-test
      runAfter:
        - fetch-source
      params:
        - name: package
          value: ./...
      workspaces:
        - name: source
          workspace: shared-workspace

    - name: build-image
      taskRef:
        name: kaniko
      runAfter:
        - run-tests
      params:
        - name: IMAGE
          value: $(params.image-name):$(tasks.fetch-source.results.commit)
      workspaces:
        - name: source
          workspace: shared-workspace
        - name: dockerconfig
          workspace: docker-credentials

    - name: deploy
      taskRef:
        name: kubectl-apply
      runAfter:
        - build-image
      params:
        - name: namespace
          value: $(params.deploy-namespace)
        - name: image
          value: $(params.image-name):$(tasks.fetch-source.results.commit)
      workspaces:
        - name: manifest-dir
          workspace: shared-workspace

runAfter defines execution order. Tasks without runAfter dependencies on each other run in parallel. $(tasks.fetch-source.results.commit) references a result from a previous task, which also creates an implicit dependency.

PipelineRuns#

Trigger a Pipeline by creating a PipelineRun:

apiVersion: tekton.dev/v1
kind: PipelineRun
metadata:
  generateName: build-test-deploy-
spec:
  pipelineRef:
    name: build-test-deploy
  params:
    - name: repo-url
      value: https://github.com/myorg/myapp.git
    - name: revision
      value: main
    - name: image-name
      value: registry.example.com/myapp
    - name: deploy-namespace
      value: staging
  workspaces:
    - name: shared-workspace
      volumeClaimTemplate:
        spec:
          accessModes:
            - ReadWriteOnce
          resources:
            requests:
              storage: 1Gi
    - name: docker-credentials
      secret:
        secretName: docker-registry-credentials

volumeClaimTemplate creates a new PVC for each PipelineRun. This gives each run isolated storage and cleans up automatically when the PipelineRun is deleted. Use this instead of a shared PVC to avoid conflicts between concurrent runs.

Workspace Backing Types#

Workspaces support multiple storage backends: volumeClaimTemplate (dynamic PVC per run), persistentVolumeClaim (existing PVC for shared caches), secret (for credentials), configMap (for configuration), and emptyDir (for scratch space). Within a Pipeline, multiple Tasks can reference the same workspace. Tekton ensures correct ordering based on runAfter declarations.

Triggers#

Tekton Triggers connect external events (webhooks from GitHub, GitLab, or any HTTP source) to PipelineRuns:

apiVersion: triggers.tekton.dev/v1beta1
kind: EventListener
metadata:
  name: github-listener
spec:
  serviceAccountName: tekton-triggers-sa
  triggers:
    - name: github-push
      interceptors:
        - ref:
            name: github
          params:
            - name: secretRef
              value:
                secretName: github-webhook-secret
                secretKey: token
            - name: eventTypes
              value: ["push"]
      bindings:
        - ref: github-push-binding
      template:
        ref: build-test-deploy-template
---
apiVersion: triggers.tekton.dev/v1beta1
kind: TriggerBinding
metadata:
  name: github-push-binding
spec:
  params:
    - name: repo-url
      value: $(body.repository.clone_url)
    - name: revision
      value: $(body.head_commit.id)
---
apiVersion: triggers.tekton.dev/v1beta1
kind: TriggerTemplate
metadata:
  name: build-test-deploy-template
spec:
  params:
    - name: repo-url
    - name: revision
  resourcetemplates:
    - apiVersion: tekton.dev/v1
      kind: PipelineRun
      metadata:
        generateName: triggered-build-
      spec:
        pipelineRef:
          name: build-test-deploy
        params:
          - name: repo-url
            value: $(tt.params.repo-url)
          - name: revision
            value: $(tt.params.revision)
          - name: image-name
            value: registry.example.com/myapp
        workspaces:
          - name: shared-workspace
            volumeClaimTemplate:
              spec:
                accessModes: ["ReadWriteOnce"]
                resources:
                  requests:
                    storage: 1Gi
          - name: docker-credentials
            secret:
              secretName: docker-registry-credentials

The EventListener creates a Kubernetes Service and Deployment that receives webhooks. The interceptor validates the webhook signature. TriggerBinding extracts parameters from the webhook payload. TriggerTemplate creates the PipelineRun with those parameters.

Tekton Hub and Catalog Tasks#

The Tekton Catalog (hub.tekton.dev) provides pre-built Tasks for common operations. Install them with the tkn CLI:

# Install git-clone task
tkn hub install task git-clone

# Install kaniko (container builds without Docker daemon)
tkn hub install task kaniko

# Install kubectl-actions
tkn hub install task kubernetes-actions

# List installed tasks
tkn task list

Catalog tasks follow standard conventions for params and workspaces. The git-clone task expects a url param and an output workspace. The kaniko task expects an IMAGE param and source workspace. Reference the Tekton Hub page for each task to see the full parameter list.

Pin catalog tasks to specific versions in production. The Hub hosts multiple versions, and upgrading should be deliberate:

tkn hub install task git-clone --version 0.9

When Blocks and Conditional Execution#

Run Tasks conditionally based on previous results or parameters:

tasks:
  - name: deploy-production
    taskRef:
      name: kubectl-apply
    when:
      - input: $(params.target-env)
        operator: in
        values: ["production"]
      - input: $(tasks.run-tests.results.pass)
        operator: in
        values: ["true"]
    runAfter:
      - build-image

All when conditions must be true for the Task to execute. If any condition fails, the Task is skipped (not failed), and the Pipeline continues.

Common Mistakes#

  1. Using a shared PVC for concurrent PipelineRuns. Two runs writing to the same PVC corrupt each other. Use volumeClaimTemplate to get a fresh PVC per run.
  2. Putting too much logic in a single Task. A Task runs in one pod. If it fails, the entire pod restarts from step 1. Break large workflows into multiple Tasks so failures are recoverable at a finer granularity.
  3. Ignoring result size limits. Results are capped at 4096 bytes. Passing a large JSON blob as a result silently truncates it. Use workspaces for anything larger than a version string or commit hash.
  4. Not setting resource requests on steps. Each step is a container. Without resource requests, the Kubernetes scheduler may place pipeline pods on undersized nodes, causing OOM kills or CPU throttling.
  5. Forgetting to create the ServiceAccount for Triggers. The EventListener needs a ServiceAccount with permissions to create PipelineRuns. Without it, webhooks arrive successfully but nothing happens, and the only evidence is in the EventListener pod logs.