Introduction
Tekton is a powerful, flexible, Kubernetes-native framework for creating CI/CD pipelines. Originally developed by Google as part of the Knative project, Tekton was donated to the Continuous Delivery Foundation (CDF) in 2019 and has since become the standard for cloud-native CI/CD in Kubernetes-centric organizations.
Unlike platform-hosted CI/CD services (GitHub Actions, CircleCI, GitLab CI), Tekton runs entirely within your Kubernetes cluster. Pipelines are defined as Kubernetes Custom Resources — they're scheduled, executed, and monitored just like any other Kubernetes workload. This means your CI/CD inherits Kubernetes' scalability, resource management, and security model.
Why Kubernetes-Native Matters
Running CI/CD as Kubernetes workloads provides several advantages: pipelines scale with your cluster (no separate CI infrastructure), pipeline pods inherit cluster RBAC and network policies, resources are managed via standard Kubernetes tooling (kubectl, Helm, GitOps), and pipeline definitions follow Kubernetes API conventions (declarative YAML, status conditions, controller patterns).
The tradeoff is complexity — Tekton requires a Kubernetes cluster and Kubernetes expertise. For teams already running Kubernetes workloads, Tekton adds no new infrastructure. For teams without Kubernetes, the learning curve is steeper than hosted alternatives.
Architecture
Tekton's architecture consists of several installable components, each extending Kubernetes with new Custom Resource Definitions (CRDs):
flowchart TD
subgraph Core["Tekton Pipelines (Core)"]
TASK[Task CRD]
PIPE[Pipeline CRD]
TR[TaskRun CRD]
PR[PipelineRun CRD]
end
subgraph Triggers["Tekton Triggers"]
EL[EventListener]
TT[TriggerTemplate]
TB[TriggerBinding]
INT[Interceptor]
end
subgraph Ecosystem["Ecosystem"]
CHAINS[Tekton Chains]
DASH[Tekton Dashboard]
CLI[tkn CLI]
HUB[Tekton Hub]
end
WEBHOOK[Git Webhook] --> EL
EL --> INT --> TB --> TT
TT -->|Creates| PR
PR -->|References| PIPE
PIPE -->|Contains| TASK
PR -->|Creates| TR
CHAINS -->|Signs| TR
DASH -->|Visualizes| PR
HUB -->|Provides| TASK
style Core fill:#132440,color:#fff
style Triggers fill:#3B9797,color:#fff
style Ecosystem fill:#16476A,color:#fff
- Tekton Pipelines — Core component. Installs Task, Pipeline, TaskRun, PipelineRun CRDs and the controller that executes them.
- Tekton Triggers — Event-driven automation. EventListeners receive webhooks and create PipelineRuns based on TriggerTemplates.
- Tekton Chains — Supply chain security. Automatically signs TaskRun/PipelineRun results and generates SLSA provenance attestations.
- Tekton Dashboard — Web UI for visualizing and managing pipelines, runs, and resources.
- tkn CLI — Command-line tool for interacting with Tekton resources (start runs, view logs, list tasks).
- Tekton Hub — Catalog of reusable Tasks and Pipelines contributed by the community.
# Install Tekton Pipelines
kubectl apply --filename https://storage.googleapis.com/tekton-releases/pipeline/latest/release.yaml
# Install Tekton Triggers
kubectl apply --filename https://storage.googleapis.com/tekton-releases/triggers/latest/release.yaml
# Install Tekton Chains
kubectl apply --filename https://storage.googleapis.com/tekton-releases/chains/latest/release.yaml
# Install Tekton Dashboard
kubectl apply --filename https://storage.googleapis.com/tekton-releases/dashboard/latest/release.yaml
# Install tkn CLI (Linux)
curl -LO https://github.com/tektoncd/cli/releases/latest/download/tkn_Linux_x86_64.tar.gz
tar xzf tkn_Linux_x86_64.tar.gz -C /usr/local/bin tkn
Tasks
A Task is the fundamental building block — a sequence of steps that run in a single pod. Each step runs as a container. Tasks are reusable, parameterized, and can produce results consumed by other tasks.
# Task: Build and push a container image
apiVersion: tekton.dev/v1
kind: Task
metadata:
name: build-push-image
spec:
params:
- name: image
type: string
description: Full image name including registry
- name: dockerfile
type: string
default: ./Dockerfile
- name: context
type: string
default: .
workspaces:
- name: source
description: Source code workspace
- name: docker-credentials
description: Docker registry credentials
results:
- name: IMAGE_DIGEST
description: Digest of the built image
- name: IMAGE_URL
description: Full URL of the pushed image
steps:
- name: build-and-push
image: gcr.io/kaniko-project/executor:latest
env:
- name: DOCKER_CONFIG
value: $(workspaces.docker-credentials.path)
args:
- --dockerfile=$(params.dockerfile)
- --context=$(workspaces.source.path)/$(params.context)
- --destination=$(params.image)
- --digest-file=$(results.IMAGE_DIGEST.path)
- name: write-url
image: alpine:3.19
script: |
#!/bin/sh
echo -n "$(params.image)" > $(results.IMAGE_URL.path)
# Task: Run unit tests
apiVersion: tekton.dev/v1
kind: Task
metadata:
name: run-tests
spec:
params:
- name: test-command
type: string
default: "npm test"
- name: coverage-threshold
type: string
default: "80"
workspaces:
- name: source
results:
- name: COVERAGE
description: Code coverage percentage
steps:
- name: install-deps
image: node:20-alpine
workingDir: $(workspaces.source.path)
script: |
#!/bin/sh
npm ci --prefer-offline
- name: run-tests
image: node:20-alpine
workingDir: $(workspaces.source.path)
script: |
#!/bin/sh
set -e
$(params.test-command) -- --coverage --coverageReporters=text
# Extract coverage percentage
COVERAGE=$(cat coverage/coverage-summary.json | \
grep -o '"pct":[0-9.]*' | head -1 | cut -d: -f2)
echo -n "$COVERAGE" > $(results.COVERAGE.path)
# Fail if below threshold
if [ "$(echo "$COVERAGE < $(params.coverage-threshold)" | bc)" -eq 1 ]; then
echo "Coverage $COVERAGE% is below threshold $(params.coverage-threshold)%"
exit 1
fi
Pipelines
A Pipeline connects multiple Tasks in a directed acyclic graph (DAG). Tasks within a pipeline can run sequentially (using runAfter) or in parallel (default). Pipelines pass data between tasks via results and workspaces.
flowchart LR
CLONE[git-clone] --> LINT[lint]
CLONE --> TEST[run-tests]
CLONE --> SCAN[security-scan]
LINT --> BUILD[build-push-image]
TEST --> BUILD
SCAN --> BUILD
BUILD --> DEPLOY[deploy-to-k8s]
style CLONE fill:#3B9797,color:#fff
style BUILD fill:#132440,color:#fff
style DEPLOY fill:#BF092F,color:#fff
# Complete CI/CD Pipeline
apiVersion: tekton.dev/v1
kind: Pipeline
metadata:
name: ci-cd-pipeline
spec:
params:
- name: repo-url
type: string
- name: revision
type: string
default: main
- name: image
type: string
- name: namespace
type: string
default: production
workspaces:
- name: shared-workspace
- name: docker-credentials
- name: kube-config
tasks:
- name: clone
taskRef:
name: git-clone
params:
- name: url
value: $(params.repo-url)
- name: revision
value: $(params.revision)
workspaces:
- name: output
workspace: shared-workspace
- name: lint
taskRef:
name: eslint
runAfter: [clone]
workspaces:
- name: source
workspace: shared-workspace
- name: test
taskRef:
name: run-tests
runAfter: [clone]
params:
- name: coverage-threshold
value: "80"
workspaces:
- name: source
workspace: shared-workspace
- name: security-scan
taskRef:
name: trivy-scanner
runAfter: [clone]
workspaces:
- name: source
workspace: shared-workspace
- name: build
taskRef:
name: build-push-image
runAfter: [lint, test, security-scan]
params:
- name: image
value: $(params.image)
workspaces:
- name: source
workspace: shared-workspace
- name: docker-credentials
workspace: docker-credentials
- name: deploy
taskRef:
name: kubectl-deploy
runAfter: [build]
params:
- name: image
value: $(params.image)@$(tasks.build.results.IMAGE_DIGEST)
- name: namespace
value: $(params.namespace)
workspaces:
- name: kube-config
workspace: kube-config
finally:
- name: notify
taskRef:
name: slack-notification
params:
- name: status
value: $(tasks.status)
- name: pipeline-name
value: $(context.pipelineRun.name)
TaskRuns & PipelineRuns
TaskRun and PipelineRun are the execution instances — they represent a single invocation of a Task or Pipeline with specific parameter values and workspace bindings. They're the Kubernetes equivalent of "running a job."
# PipelineRun — execute the CI/CD pipeline
apiVersion: tekton.dev/v1
kind: PipelineRun
metadata:
generateName: ci-cd-run-
spec:
pipelineRef:
name: ci-cd-pipeline
params:
- name: repo-url
value: https://github.com/myorg/myapp.git
- name: revision
value: abc123def
- name: image
value: registry.example.com/myapp:v1.2.3
- name: namespace
value: production
workspaces:
- name: shared-workspace
volumeClaimTemplate:
spec:
accessModes: [ReadWriteOnce]
resources:
requests:
storage: 1Gi
- name: docker-credentials
secret:
secretName: docker-registry-creds
- name: kube-config
secret:
secretName: kubeconfig-production
taskRunTemplate:
serviceAccountName: pipeline-runner
timeouts:
pipeline: 30m
tasks: 20m
# Using tkn CLI to start and monitor runs
# Start a pipeline run
tkn pipeline start ci-cd-pipeline \
--param repo-url=https://github.com/myorg/myapp.git \
--param revision=main \
--param image=registry.example.com/myapp:latest \
--workspace name=shared-workspace,claimName=pipeline-pvc \
--workspace name=docker-credentials,secret=docker-creds \
--showlog
# List recent pipeline runs
tkn pipelinerun list
# Get logs from a specific run
tkn pipelinerun logs ci-cd-run-abc123 -f
# Describe run status
tkn pipelinerun describe ci-cd-run-abc123
Triggers
Tekton Triggers enable event-driven pipeline execution. When a Git webhook fires, the EventListener receives it, an Interceptor validates and filters the event, a TriggerBinding extracts parameters, and a TriggerTemplate creates the PipelineRun.
# EventListener — receives webhooks
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]
- ref:
name: cel
params:
- name: filter
value: "body.ref == 'refs/heads/main'"
bindings:
- ref: github-push-binding
template:
ref: ci-cd-template
---
# TriggerBinding — extract data from webhook payload
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.after)
- name: repo-name
value: $(body.repository.name)
---
# TriggerTemplate — creates PipelineRun from extracted data
apiVersion: triggers.tekton.dev/v1beta1
kind: TriggerTemplate
metadata:
name: ci-cd-template
spec:
params:
- name: repo-url
- name: revision
- name: repo-name
resourcetemplates:
- apiVersion: tekton.dev/v1
kind: PipelineRun
metadata:
generateName: $(tt.params.repo-name)-run-
spec:
pipelineRef:
name: ci-cd-pipeline
params:
- name: repo-url
value: $(tt.params.repo-url)
- name: revision
value: $(tt.params.revision)
- name: image
value: registry.example.com/$(tt.params.repo-name):$(tt.params.revision)
workspaces:
- name: shared-workspace
volumeClaimTemplate:
spec:
accessModes: [ReadWriteOnce]
resources:
requests:
storage: 1Gi
Workspaces
Workspaces provide shared storage for steps within a Task and between Tasks in a Pipeline. They abstract the storage mechanism — the same Pipeline can use PVCs in production and emptyDirs in testing.
PersistentVolumeClaim: Durable storage that persists across step/pod restarts. Required when Tasks in a Pipeline need to share large artifacts (source code, build outputs). Use volumeClaimTemplate for per-run PVCs or reference existing claims.
emptyDir: Ephemeral, pod-local storage. Fast but lost when the TaskRun pod terminates. Good for scratch space within a single Task. Can be memory-backed for extremely fast I/O.
ConfigMap: Read-only configuration data. Useful for providing config files (nginx.conf, settings) to pipeline steps.
Secret: Read-only sensitive data. Required for credentials (Docker registry auth, kubeconfig, API keys).
# Workspace binding examples in a PipelineRun
spec:
workspaces:
# PVC template (created per run, auto-cleaned)
- name: shared-workspace
volumeClaimTemplate:
spec:
accessModes: [ReadWriteOnce]
storageClassName: fast-ssd
resources:
requests:
storage: 5Gi
# Existing PVC (shared across runs — use with caution)
- name: maven-cache
persistentVolumeClaim:
claimName: maven-repo-cache
# emptyDir (fast, ephemeral)
- name: temp-workspace
emptyDir: {}
# Memory-backed emptyDir (RAM disk)
- name: fast-scratch
emptyDir:
medium: Memory
sizeLimit: 256Mi
# Secret for credentials
- name: docker-credentials
secret:
secretName: registry-auth
# ConfigMap for configuration
- name: app-config
configMap:
name: pipeline-config
Tekton Chains
Tekton Chains provides supply chain security by automatically signing TaskRun and PipelineRun results and generating SLSA provenance attestations. It proves that artifacts were produced by specific pipelines from specific source commits — a critical requirement for software supply chain compliance.
# Configure Tekton Chains for cosign signing
kubectl patch configmap chains-config -n tekton-chains -p='{"data":{
"artifacts.taskrun.format": "in-toto",
"artifacts.taskrun.storage": "oci",
"artifacts.oci.storage": "oci",
"transparency.enabled": "true",
"signers.x509.fulcio.enabled": "true"
}}'
# Verify a signed image with cosign
cosign verify \
--certificate-identity="https://tekton.dev/chains/*" \
--certificate-oidc-issuer="https://token.actions.githubusercontent.com" \
registry.example.com/myapp:v1.2.3
# View SLSA provenance
cosign verify-attestation \
--type slsaprovenance \
--certificate-identity-regexp=".*" \
--certificate-oidc-issuer-regexp=".*" \
registry.example.com/myapp:v1.2.3 | jq '.payload' | base64 -d | jq .
Custom Tasks
Tekton's extensibility allows creating custom Task types via Kubernetes controllers. Custom Tasks handle operations that don't fit the standard step-based model — approval gates, external service calls, or specialized execution environments.
# Custom Task: Approval Gate
apiVersion: custom.tekton.dev/v1alpha1
kind: ApprovalTask
metadata:
name: production-approval
spec:
approvers:
- team: platform-engineering
- team: security
minimumApprovals: 2
timeout: 24h
message: |
Deployment to production requires approval.
Pipeline: $(context.pipelineRun.name)
Image: $(params.image)
---
# Using custom task in a Pipeline
apiVersion: tekton.dev/v1
kind: Pipeline
metadata:
name: deploy-with-approval
spec:
tasks:
- name: build
taskRef:
name: build-push-image
- name: approve
taskRef:
apiVersion: custom.tekton.dev/v1alpha1
kind: ApprovalTask
runAfter: [build]
- name: deploy
taskRef:
name: kubectl-deploy
runAfter: [approve]
Platform Comparison
Understanding where Tekton fits relative to other CI/CD platforms helps inform adoption decisions:
| Dimension | Tekton | GitHub Actions | Jenkins | Argo Workflows |
|---|---|---|---|---|
| Runtime | Kubernetes pods | Hosted VMs | Agent JVM | Kubernetes pods |
| Definition | Kubernetes CRDs | YAML in .github/ | Jenkinsfile | Kubernetes CRDs |
| Scaling | Kubernetes native | GitHub-managed | Manual/plugins | Kubernetes native |
| Supply Chain | Chains (SLSA L3) | Attestations (SLSA L3) | Plugins | Limited |
| Best For | Platform teams, compliance | GitHub-native projects | Legacy enterprise | Complex DAGs, ML |
| Setup Effort | High (K8s required) | Zero | Medium | High (K8s required) |
| Ecosystem | Tekton Hub | Marketplace (largest) | Plugins (extensive) | Limited |
Production Patterns
Running Tekton in production requires attention to multi-tenancy, resource management, and operational patterns:
# Multi-tenant pipeline with resource quotas
apiVersion: v1
kind: ResourceQuota
metadata:
name: team-a-pipeline-quota
namespace: team-a-pipelines
spec:
hard:
requests.cpu: "8"
requests.memory: 16Gi
limits.cpu: "16"
limits.memory: 32Gi
persistentvolumeclaims: "10"
pods: "20"
---
# LimitRange for pipeline pods
apiVersion: v1
kind: LimitRange
metadata:
name: pipeline-limits
namespace: team-a-pipelines
spec:
limits:
- type: Container
default:
cpu: 500m
memory: 512Mi
defaultRequest:
cpu: 100m
memory: 128Mi
max:
cpu: "4"
memory: 8Gi
# Pipeline-as-Code with Tekton (using Pipelines-as-Code controller)
# .tekton/pull-request.yaml — lives in the application repo
apiVersion: tekton.dev/v1
kind: PipelineRun
metadata:
name: pull-request
annotations:
pipelinesascode.tekton.dev/on-event: "[pull_request]"
pipelinesascode.tekton.dev/on-target-branch: "[main]"
pipelinesascode.tekton.dev/max-keep-runs: "5"
spec:
pipelineSpec:
tasks:
- name: clone
taskRef:
name: git-clone
kind: ClusterTask
params:
- name: url
value: "{{ repo_url }}"
- name: revision
value: "{{ revision }}"
workspaces:
- name: output
workspace: source
- name: test
runAfter: [clone]
taskSpec:
steps:
- image: node:20
script: |
cd $(workspaces.source.path)
npm ci
npm test
workspaces:
- name: source
workspaces:
- name: source
workspace: source
workspaces:
- name: source
volumeClaimTemplate:
spec:
accessModes: [ReadWriteOnce]
resources:
requests:
storage: 1Gi
flowchart TD
subgraph Cluster["Kubernetes Cluster"]
subgraph Control["Control Plane"]
TC[Tekton Controller]
TT[Tekton Triggers]
TD[Tekton Dashboard]
end
subgraph TeamA["Namespace: team-a-pipelines"]
PA[PipelineRun A]
QA[ResourceQuota]
end
subgraph TeamB["Namespace: team-b-pipelines"]
PB[PipelineRun B]
QB[ResourceQuota]
end
subgraph Shared["Namespace: tekton-shared"]
CT[ClusterTasks]
SEC[Shared Secrets]
end
end
TC --> PA
TC --> PB
CT -.-> PA
CT -.-> PB
style Control fill:#132440,color:#fff
style TeamA fill:#3B9797,color:#fff
style TeamB fill:#16476A,color:#fff
style Shared fill:#BF092F,color:#fff
- Namespace isolation — Each team gets their own namespace with ResourceQuotas and LimitRanges. ClusterTasks provide shared reusable tasks.
- PriorityClasses — Assign PriorityClasses to pipeline pods so production deploys preempt dev builds during resource pressure.
- Cleanup policies — Use Tekton's built-in pruner or CronJobs to delete completed PipelineRuns after retention period.
- Affinity/tolerations — Route pipeline workloads to dedicated node pools to avoid interfering with application pods.
Exercises
Install Tekton Pipelines in a local Kind/k3d cluster. Create Tasks for: git-clone (from Tekton Hub), running Go tests, building a container image with Kaniko, and deploying to the same cluster. Wire them into a Pipeline with proper workspace sharing. Create a PipelineRun and verify the application deploys successfully.
Set up Tekton Triggers with a GitHub EventListener. Configure an Interceptor to validate webhook signatures and filter for push events on the main branch. Create TriggerBinding and TriggerTemplate that extract the commit SHA and repository URL, then automatically create PipelineRuns. Test by pushing a commit and verifying the pipeline starts.
Install Tekton Chains alongside your pipeline. Configure it to sign TaskRun results using cosign (keyless with Fulcio). Build and push a container image, then verify the signature and SLSA provenance attestation using cosign verify and cosign verify-attestation. Document what SLSA level your setup achieves.
Design a multi-tenant Tekton platform for 3 teams. Create separate namespaces with ResourceQuotas (CPU: 4 cores, Memory: 8GB per team). Implement ClusterTasks for common operations (git-clone, docker-build, kubectl-deploy). Set up RBAC so each team can only create PipelineRuns in their namespace but can reference shared ClusterTasks. Add PriorityClasses so production deploys take precedence over CI builds.