Run TektonPipelines from containers

Tekton Pipelines is a CI/CD tool that runs on a Kubernetes cluster.

While it’s common to operate Tekton using a dashboard or CLI, you can also run tasks using a more primitive way: by directly submitting a PipelineRun resource, one of Tekton’s Custom Resource Definitions (CRDs), to the Kubernetes API server.

For a minimal configuration:

FRAGMENT=$(cat /dev/urandom | base64 | tr -dC '[:alnum:]' | tr '[A-Z]' '[a-z]' | fold -w 5 | head -n 1)

cat << EOF | kubectl apply -f -
apiVersion: tekton.dev/v1
kind: PipelineRun
metadata:
  name: <some-pipelinerun-name>-$FRAGMENT
spec:
  pipelineRef:
  name: <some-pipeline-name>
EOF

A manifest for PipelineRun is written within the here document.
In practice, you’ll also likely specify metadata.namespace, spec.params, and spec.workspaces.

$FRAGMENT is a work example of generating an identifier to avoid duplication for repeated executions.

Additionally, this method assumes you have the necessary permissions, such as the authority to create Tekton Pipelines resources, when executing kubectl.

This approach isn’t very useful if Tekton Triggers is set up and working correctly.
However, since setting up Triggers can also be quite complex, this method is a viable option if you want to use Pipelines on their own without the additional complexity.

Run inside containers

CronJob is a typical use case for launching a Pipeline from within a container.
If you have kubectl installed in the container image and the necessary permissions are provided via a ServiceAccount, it can behave just like an operation performed on the host machine.

Here’s an example manifest:

apiVersion: batch/v1
kind: CronJob
metadata:
  name: <some-job-name>
spec:
  jobTemplate:
    spec:
      template:
        spec:
          serviceAccountName: <some-serviceaccount>
          containers:
          - name: create-pipeline
            image: <some-image-name-having-kubectl>
            env:
            - name: PODNAME
              valueFrom:
                fieldRef:
                  fieldPath: metadata.name
            command: ["bash"]
            args:
            - "-c"
            - |
              cat <<EOF | kubectl apply -f -
              apiVersion: tekton.dev/v1
              kind: PipelineRun
              metadata:
                name: $PODNAME
              spec:
                pipelineRef:
                  name: <some-pipeline-name>
              EOF

Here are 2 key characteristics:

  • It specifies a ServiceAccount on spec.jobTemplate.spec.template.spec.serviceAccontName.
  • It omits the need to generate a $FRAGMENT by reusing the job’s name via the Downward API. While not mandatory, this makes it easier to correlate each PipelineRun with its corresponding job.

ServiceAccont

By pre-registering a set of ServiceAccount, Role, and RoleBinding within your cluster, you can provide the necessary permissions to containers:

apiVersion: v1
kind: ServiceAccount
metadata:
  name: <some-serviceaccount>
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: tekton-pipelines-operator
rules:
- apiGroups: ["tekton.dev"]
  resources: ["pipelineruns", "taskruns"]
  verbs: ["get", "list", "create"]
- apiGroups: ["tekton.dev"]
  resources: ["pipeline"]
  verbs: ["get", "list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: pipeline-operator-binding
subjects:
- kind: ServiceAccount
  name: <some-serviceaccount>
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: tekton-pipelines-operator

Read access to Pipelines will also be required due to spec.pipelineRef.

Run as a non-root user inside the container

Since a CronJob runs as the container’s user, simply specifying a ServiceAccount allows kubectl to acquire the necessary resources for access.

For special cases, such as running as a non-root user, additional measures like the following are required:

export KUBERNETES_SERVICE_HOST=<some-host-address>
export KUBERNETES_SERVICE_PORT=<some-host-port>

FRAGMENT=$(cat /dev/urandom | base64 | tr -dC '[:alnum:]' | tr '[A-Z]' '[a-z]' | fold -w 5 | head -n 1)

cat << EOF | sudo -E kubectl apply -f -
apiVersion: tekton.dev/v1
kind: PipelineRun
metadata:
  name: <some-pipelinerun-name>-$FRAGMENT
spec:
  pipelineRef:
  name: <some-pipeline-name>
EOF

When running as a non-root user, there are 2 main differences from running as root:

  • Since tokens are provided for the root user, you’ll need to install sudo in your container image and set it up for the non-root execution user.
  • The API server address that kubectl references is registered in the root user’s environment variables, so you need to configure it for the non-root user’s environments.

While you could set up the tokens directly for the execution user without using sudo, the required effort would be similar.

⁋ Sep 9, 2025↻ Sep 9, 2025
中馬崇尋
Chuma Takahiro