Tekton, the Kubernetes-native CI/CD framework, gets a lot of buzz, but its core components—Tasks, Pipelines, and Runs—are deceptively simple and elegantly orchestrated.
Let’s see a basic example in action. Imagine you want to build a Docker image and push it to a registry.
Here’s a Task that does just that:
apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
name: build-and-push
spec:
params:
- name: IMAGE_URL
description: URL of the image to build and push (e.g., docker.io/myuser/myimage:latest)
steps:
- name: build
image: gcr.io/cloud-builders/docker
script: |
docker build -t $(params.IMAGE_URL) .
- name: push
image: gcr.io/cloud-builders/docker
script: |
echo "$$(DOCKER_PASSWORD)" | docker login -u "$$(DOCKER_USERNAME)" --password-stdin
docker push $(params.IMAGE_URL)
env:
- name: DOCKER_USERNAME
valueFrom:
secretKeyRef:
name: docker-credentials
key: username
- name: DOCKER_PASSWORD
valueFrom:
secretKeyRef:
name: docker-credentials
key: password
This Task defines a sequence of steps. Each step is essentially a container that runs a command. The build-and-push task takes an IMAGE_URL parameter and uses the gcr.io/cloud-builders/docker image to first build the Docker image from the current directory (.) and then push it. Notice how it securely pulls Docker credentials from a Kubernetes Secret named docker-credentials.
Now, a Task is a reusable unit of work, but it doesn’t do anything on its own. To run a Task, you create a TaskRun.
Here’s a TaskRun that executes our build-and-push task:
apiVersion: tekton.dev/v1beta1
kind: TaskRun
metadata:
name: build-and-push-run-1
spec:
taskRef:
name: build-and-push
params:
- name: IMAGE_URL
value: "docker.io/myuser/myimage:v1.0.0"
# To run this, you'd typically have a workspace defined
# for the source code, e.g.:
# workspaces:
# - name: source-code
# persistentVolumeClaim:
# claimName: my-source-pvc
When this TaskRun is created, Tekton provisions the necessary Kubernetes resources (like Pods) to execute the steps defined in the build-and-push Task, passing in the specified IMAGE_URL. The taskRef.name points to the Task we want to run. If your task needed access to source code, you’d define a workspace to provide that.
A Pipeline is where things get interesting. It’s a collection of Tasks orchestrated in a directed acyclic graph (DAG). This allows you to define multi-step workflows.
Let’s define a Pipeline that first clones a Git repository, then builds and pushes the Docker image:
apiVersion: tekton.dev/v1beta1
kind: Pipeline
metadata:
name: build-push-pipeline
spec:
params:
- name: IMAGE_URL
description: URL of the image to build and push
- name: GIT_REPO_URL
description: URL of the Git repository
tasks:
- name: clone-repo
taskRef:
name: git-clone # Assuming a pre-defined git-clone Task
params:
- name: url
value: $(params.GIT_REPO_URL)
# A workspace is needed to output the cloned code
workspaces:
- name: output
workspace: source-code-workspace # A PipelineWorkspace defined below
- name: build-and-push-image
taskRef:
name: build-and-push
runAfter: # This task runs only after clone-repo succeeds
- clone-repo
params:
- name: IMAGE_URL
value: $(params.IMAGE_URL)
workspaces:
- name: source-code # This maps to the workspace expected by build-and-push Task
workspace: source-code-workspace # The same PipelineWorkspace
workspaces: # Defines the shared workspaces for the pipeline
- name: source-code-workspace
Here, the build-push-pipeline takes IMAGE_URL and GIT_REPO_URL as parameters. It has two tasks: clone-repo (which we assume is a pre-existing Tekton Task for cloning Git repos) and our build-and-push Task. The runAfter: - clone-repo directive ensures that the build-and-push-image task only executes after clone-repo has completed successfully. Notice how the source-code-workspace is defined at the Pipeline level and then mapped to the individual Tasks’ workspaces. This is how data (like cloned source code) is shared between tasks in a Pipeline.
To run a Pipeline, you create a PipelineRun.
apiVersion: tekton.dev/v1beta1
kind: PipelineRun
metadata:
name: build-push-pipeline-run-1
spec:
pipelineRef:
name: build-push-pipeline
params:
- name: IMAGE_URL
value: "docker.io/myuser/myimage:v1.0.1"
- name: GIT_REPO_URL
value: "https://github.com/myuser/myrepo.git"
workspaces:
- name: source-code-workspace
persistentVolumeClaim:
claimName: my-source-pvc # A PVC to store the cloned code
This PipelineRun triggers the execution of the build-push-pipeline. Tekton will manage the scheduling and execution of the clone-repo and build-and-push-image tasks in the correct order, passing parameters and sharing workspaces as defined. The workspaces section here links the Pipeline’s declared source-code-workspace to a specific Kubernetes PersistentVolumeClaim (PVC) that will be used for storage.
The most surprising thing about Tekton’s Task and Pipeline definitions is that they are purely declarative blueprints; they don’t contain any execution logic themselves. The actual work happens when a TaskRun or PipelineRun is created, which instructs the Tekton Controller to spin up Kubernetes Pods to execute the steps defined in the referenced Task or Pipeline. This separation of definition from execution is key to Tekton’s flexibility and Kubernetes-native nature.
The next logical step is to explore how to handle more complex dependencies between tasks, including conditional execution and parallel branches within a Pipeline.