Whitepaper: Trilio Site Recovery (TSR) — DR for Kubernetes-native VMs

If you’ve managed traditional, centralized CI/CD platforms, you have likely run into a common architectural mismatch when moving to cloud-native infrastructure. Older, server-based systems were fundamentally built around persistent state and static build nodes. They do an excellent job in virtual machine environments, but as your development teams grow and cluster demand spikes, scaling a centralized controller server becomes a complex project of its own. You often end up dealing with resource bottlenecks during peak deployment hours and idle compute waste when the system is quiet.

OpenShift Pipelines offers a different approach, providing a truly cloud-native CI/CD solution. Instead of relying on a central server to orchestrate jobs, it integrates directly into the Kubernetes framework. Every step of your build and deploy process runs in its own ephemeral container. It spins up exactly when needed, executes its task, and disappears. This gives you independent scaling, precise resource management, and a declarative setup that lives entirely in Git alongside your code.

This article is designed for DevOps engineers and architects who need to transition to a declarative, container-native workflow that integrates directly into the OpenShift console. We move past the theory to focus on the practical mechanics of modular tasks and automated triggers to help you build a production-ready delivery system.

Summary of key best practices for OpenShift Pipelines

The following table summarizes the key OpenShift Pipelines best practices discussed in this article.

Best practice

Description

Standardize reusable tasks

Build modular, single-purpose Tasks (like build or scan logic) that can be dropped into any pipeline without duplicating code.

Implement event-driven automation

Move past manual Tekton commands by using EventListeners and Triggers to kick off builds automatically.

Keep secrets out of your YAML

Link annotated Kubernetes Secrets to dedicated ServiceAccounts to handle private registry auth without hardcoding keys in YAML.

Make security a “hard stop”

Configure security tools like Trivy to fail the pipeline immediately if critical vulnerabilities are found, preventing insecure deployments.

Persist audit metadata

Use the Results API to store scan statuses and image digests in the cluster’s permanent datastore rather than relying on ephemeral pod logs.

Use dedicated ServiceAccounts

Use specific ServiceAccounts for pipeline execution to maintain granular control over permissions and access.

Automated Application-Centric Red Hat OpenShift Data Protection & Intelligent Recovery

Why OpenShift needs a native CI/CD solution

Kubernetes fundamentally changed how companies ship code, but the CI/CD tools they use haven’t always kept up. If you try to force a legacy, server-based delivery tool into a cluster, it results in a lot of friction and manual configuration. 

OpenShift Pipelines, based on the open-source Tekton project, is the fix for this problem. Instead of being an external tool bolted on, it’s built on Tekton and operates as a native cluster service. By using standard Kubernetes CRDs, your pipelines become part of the platform itself, inheriting the same security and management policies you’re already using for your production workloads.

This architectural shift addresses several fundamental challenges in modern DevOps:

  • Scalability and resource efficiency: In older models, you often have to maintain static build nodes that sit idle, consuming CPU and memory even when no code is being built. Because OpenShift Pipelines is based on Tekton, it uses an “on-demand” model. Each step of your pipeline runs in its own ephemeral container. When the work is complete, the pod is terminated, and its resources are immediately returned to the cluster pool. This guarantees that you only use what you need, exactly when you need it. 
  • The “everything-as-code” paradigm: One of the primary benefits of a Tekton-based system is its declarative nature. Your entire pipeline, from the individual tasks to the overarching workflow, is defined in YAML. This eliminates the “Snowflake” problem where build configurations live in an opaque database or UI. Instead, your CI/CD infrastructure is version-controlled, peer-reviewed, and managed via the same GitOps processes as your production code.
  • Security and isolation: Standardizing on Kubernetes RBAC is where native pipelines really shine. In the old way of doing things, you’d have one giant, centralized engine with “god-mode” permissions across your whole cluster. With OpenShift Pipelines, you can ditch that risk. You run each pipeline under its own dedicated ServiceAccount. This lets you box in build processes to specific namespaces and hammer home the principle of least privilege. Your CI/CD perms end up being just as tight as the production environment they’re deploying to.

Understanding the architecture of OpenShift Pipelines

OpenShift Pipelines relies on several foundational Tekton building blocks. To build a functional CI/CD system here, you need to understand how these individual components interact to form a complete workflow. 

Here is a breakdown of the Tekton objects you will be managing in your manifests:

  • Step: This is the smallest unit of execution in OpenShift Pipelines. It is a container running a specific command.
  • Task: A sequential collection of Steps. A Task executes as a single Pod, meaning that all steps share the same local environment.
  • Pipeline: A directed acyclic graph (DAG) of Tasks. This defines the overall workflow and the flow of data between stages.
  • TaskRun and PipelineRun: Tasks and Pipelines are just static templates. A TaskRun executes a single Task, while a PipelineRun executes an entire Pipeline. These are the active objects that bind your generic templates to specific Git repos, image registries, and runtime parameters.

The ephemeral pod problem and workspaces

Since Tasks map to Pods, you’re working in a stateless environment. These containers spin down the moment they finish, wiping any local data. If your first Task pulls a repository and the second needs to compile it, the second Pod can’t access the first one’s disk. Once the clone step is done, the cluster kills that Pod and its filesystem. You have to hand off that source code before the container disappears.

This is where Workspaces can help. A workspace is just an abstraction over a PersistentVolumeClaim (PVC). You define it in the Pipeline, and Tekton mounts that same PVC into every Pod in the sequence. This provides a shared, persistent disk that survives the Pod lifecycle, letting you pass data from one stage to the next.

Steps to deploying OpenShift Pipelines

One neat thing about working with OpenShift is that everything comes packaged as an operator, and OpenShift Pipelines is no different. To start with the demo, we’ll install the OpenShift Pipelines operator from the software catalog.

Prerequisites

Before proceeding with the install:

  • Make sure that you have access to the OpenShift Container Platform cluster and with cluster-admin privileges.
  • In addition to the “oc” CLI, you need the Tekton CLI (tkn) to work with Tekton. The “tkn” CLI can be downloaded from here.

After extracting the archive, make sure that the location of the “tkn” binary is present in the PATH variable.

				
					$ tar xvzf tkn-linux-amd64.tar.gz
$ echo $PATH

				
			

Installing the OpenShift Pipelines operator

Follow these steps:

  1. Access OperatorHub: In the Administrator perspective, navigate to Ecosystem > Software Catalog.
  2. Locate the Operator: Use the search bar to find Red Hat OpenShift Pipelines. Click the tile and then select Install.
  3. Configure installation settings:
    • Installation mode: Choose all namespaces on the cluster. This makes the Operator active globally across your environment.
    • Approval strategy: Select Automatic to allow the Operator Lifecycle Manager (OLM) to manage future updates without manual intervention.
    • Update channel: Select the latest channel to run the most recent stable version. If your project requires a specific environment, you can select a versioned channel instead.
  4. Finalize: Click Install and wait for the status to change to “Succeeded.”

Once the installation is complete, verify that the Tekton controller pods are running. The Operator automatically sets up the global configuration and  default storage settings.

				
					$ oc get all -n openshift-pipelines
Warning: apps.openshift.io/v1 DeploymentConfig is deprecated in v4.14+, unavailable in v4.10000+
NAME                                                         READY   STATUS      RESTARTS   AGE
pod/pipelines-as-code-controller-6b558d64d5-5rwcm            1/1     Running     1          5m
pod/pipelines-as-code-watcher-565468b9c5-sgsh6               1/1     Running     1          5m
pod/pipelines-as-code-webhook-7ff9f844cc-9qgk6               1/1     Running     1          5m
pod/pipelines-console-plugin-5b9c658745-8w9x5                1/1     Running     1          5m
pod/tekton-chains-controller-69b9f88cbb-4mmjg                1/1     Running     1          5m
pod/tekton-events-controller-7cc94db76d-mrfhc                1/1     Running     1          5m
pod/tekton-operator-proxy-webhook-8b648679d-gld7s            1/1     Running     1          5m
pod/tekton-pipelines-controller-7db895d4cc-j9z66             1/1     Running     1          5m
pod/tekton-pipelines-remote-resolvers-cd7ff8dfd-blp6p        1/1     Running     1          5m
pod/tekton-pipelines-webhook-6d5b676c-9bslm                  1/1     Running     1          5m
[.................]    

				
			

OpenShift Pipelines often include a set of preinstalled Tasks for common operations such as S2I builds, Git cloning, and container image management. All of these tasks are available in the openshift-pipelines project.

				
					$ tkn task ls -n openshift-pipelines
NAMESPACE               NAME                        DESCRIPTION              AGE
openshift-pipelines     argocd-task-sync-and-wait   This task syncs (de...   1 day ago
openshift-pipelines     buildah                     
Buildah task build...   1 day ago
openshift-pipelines     buildah-1-22-0   
Buildah task build...   1 day ago
openshift-pipelines     buildah-ns   
[...............]

				
			

You can get more information about a task as follows.

				
					$ tkn -n openshift-pipelines task describe buildah
Name:          buildah
Namespace:     openshift-pipelines
Description:   
Buildah task builds source into a container image and
then pushes it to a container registry.

Version:       0.8.0
Annotations:
 artifacthub.io/category=integration-delivery
 artifacthub.io/maintainers=- name: OpenShift Pipeline task maintainers
  email: pipelines-extcomm@redhat.com

 artifacthub.io/provider=Red Hat
 artifacthub.io/recommendations=- url: https://tekton.dev/

[..................]

 Params

 NAME                 TYPE     DESCRIPTION              DEFAULT VALUE
 ∙ IMAGE              string   Fully qualified con...   ---
 ∙ DOCKERFILE         string   Path to the `Docker...   ./Dockerfile
 ∙ BUILD_ARGS         array    Dockerfile build ar...   []
 ∙ CONTEXT            string   Path to the directo...   .
 ∙ STORAGE_DRIVER     string   Set buildah storage...   vfs
 ∙ FORMAT             string   The format of the b...   oci
 ∙ BUILD_EXTRA_ARGS   string   Extra parameters pa...   
 ∙ PUSH_EXTRA_ARGS    string   Extra parameters pa...   
 ∙ SKIP_PUSH          string   Skip pushing the im...   false
 ∙ TLS_VERIFY         string   Sets the TLS verifi...   true
 ∙ VERBOSE            string   Turns on verbose lo...   false
[................]

				
			

Automated Red Hat OpenShift Data Protection & Intelligent Recovery

Perform secure application-centric backups of containers, VMs, helm & operators

Use pre-staged snapshots to instantly test, transform, and restore during recovery

Scale with fully automated policy-driven backup-and-restore workflows

Writing your first Pipeline

Let’s write our first “Hello World” pipeline to get things up and running. This Pipeline involves defining a reusable Task, wrapping it in a Pipeline, and finally executing it with a PipelineRun.

Start by creating the Task definition for the demo pipeline.

				
					apiVersion: tekton.dev/v1
kind: Task
metadata:
  name: hello-world-task
spec:
  steps:
    - name: say-hello
      image: registry.access.redhat.com/ubi9/ubi-micro
      script: |
        #!/usr/bin/env bash
        echo "Hello, World! This is running on OpenShift Pipelines."
				
			

Apply the manifest.

				
					$ oc apply -f hello-task.yaml 
task.tekton.dev/hello-world-task created

				
			

A Pipeline connects one or more tasks. Even for a “Hello World” demo, this abstraction is necessary to show the workflow. Create the Pipeline as follows.

				
					apiVersion: tekton.dev/v1
kind: Pipeline
metadata:
  name: hello-world-pipeline
spec:
  tasks:
    - name: hello-task-reference
      taskRef:
        name: hello-world-task
				
			

Apply the manifest.

				
					$ oc apply -f hello-pipeline.yaml 
pipeline.tekton.dev/hello-world-pipeline created

				
			

The pipeline run can also be triggered with the “oc” command after creating a PipelineRun object, but we’ll use the “tkn” binary downloaded earlier.

				
					$ tkn pipeline start hello-world-pipeline --showlog
PipelineRun started: hello-world-pipeline-run-vg6mt
Waiting for logs to be available...
[hello-task-reference : say-hello] Hello, World! This is running on OpenShift Pipelines.

				
			

Advanced automation with Tekton Triggers and event handling

Manual testing works for a start, but real-world CI/CD needs to be event-driven. By using Tekton Triggers, your OpenShift cluster can listen for Git pushes or Pull Requests and automatically kick off pipelines, shifting your workflow from manual execution to a true cloud-native automation model.

Core components of automation

To transition from manual to automated execution, four specific Tekton objects are required:

  • EventListener: A long-running pod that exposes an HTTP endpoint (“sink”) to receive incoming webhooks from platforms like GitHub or GitLab.
  • Trigger: The logic that connects the EventListener to specific bindings and templates.
  • TriggerBinding: A mapping tool that extracts specific data from the incoming JSON payload (e.g., commit SHAs or repository URLs) and converts it into parameters.
  • TriggerTemplate: A blueprint that defines the resource to be created (typically a PipelineRun) and specifies how to inject the parameters extracted by the binding.

Demo: An environment-agnostic automated workflow

In a standard production setup, a Git server automatically sends a webhook to the cluster. However, to ensure that this demo is functional in any environment, including local instances where external connectivity might be restricted, we will use a Simulated Trigger approach.

Instead of relying on an external Git provider, we will use a “curl” command to send a JSON payload directly to the internal listener. This mimics the exact behavior of a Git webhook, allowing you to validate the automation logic without the overhead of networking tunnels.

The following demo builds on the foundational “Hello World” setup from the previous demo. By reusing the existing Task and Pipeline definitions, we transition the manual workflow to a fully automated, event-driven system.

The TriggerBinding captures the repository URL from our simulated payload.

				
					apiVersion: triggers.tekton.dev/v1beta1
kind: TriggerBinding
metadata:
  name: internal-push-binding
spec:
  params:
    - name: git-repo-url
      value: $(body.repository.url)
				
			

Create the binding.

				
					$ oc apply -f git-binding.yaml
triggerbinding.triggers.tekton.dev/internal-push-binding created

				
			

Define the TriggerTemplate, which tells Tekton to create a PipelineRun whenever the trigger is fired.

				
					apiVersion: triggers.tekton.dev/v1beta1
kind: TriggerTemplate
metadata:
  name: internal-push-template
spec:
  params:
    - name: git-repo-url
  resourcetemplates:
    - apiVersion: tekton.dev/v1
      kind: PipelineRun
      metadata:
        generateName: automated-hello-run-
      spec:
        pipelineRef:
          name: hello-world-pipeline
				
			

Create the template.

				
					$ oc apply -f git-template.yaml
triggertemplate.triggers.tekton.dev/internal-push-template created

				
			

Create an EventListener, which will remain idle until it receives an HTTP request.

				
					apiVersion: triggers.tekton.dev/v1beta1
kind: EventListener
metadata:
  name: internal-listener
spec:
  triggers:
    - name: internal-trigger
      bindings:
        - ref: internal-push-binding
      template:
        ref: internal-push-template
				
			

Create the event listener.

				
					$ oc apply -f git-listener.yaml
eventlistener.triggers.tekton.dev/internal-listener created

				
			

You can list down the created EventListener, triggerTemplate and triggerBinding as follows.

				
					$ tkn eventlistener list
NAME                AGE          URL                                                     AVAILABLE
internal-listener   1 hour ago   http://el-internal-listener.p1.svc.cluster.local:8080   True

$ tkn triggerbinding list
NAME                    AGE
internal-push-binding   1 hour ago

$ tkn triggertemplate list
NAME                     AGE
internal-push-template   1 hour ago

$ tkn triggertemplate describe internal-push-template
Name:        internal-push-template
Namespace:   p1

⚓ Params

 NAME             DESCRIPTION   DEFAULT VALUE
 ∙ git-repo-url                 ---

📦 ResourceTemplates

 NAME    GENERATENAME           KIND          APIVERSION
 ∙ ---   automated-hello-run-   PipelineRun   tekton.dev/v1beta1

				
			

Expose the service to the network to allow the simulation command to reach the listener.

				
					$ oc expose svc el-internal-listener
route.route.openshift.io/el-internal-listener exposed
$ LISTENER_URL=$(oc get route el-internal-listener -o jsonpath='{.spec.host}')

				
			

Run the following curl command to trigger the automation sequence. This sends the JSON data that the TriggerBinding is configured to extract.

				
					$ url -v -X POST http://$LISTENER_URL   -H "Content-Type: application/json"   -d '{
    "repository": {
      "url": "https://internal-git.example.com/demo-app.git"
    }
  }'
Note: Unnecessary use of -X or --request, POST is already inferred.
* Host el-internal-listener-p1.apps-crc.testing:80 was resolved.
* IPv6: (none)
* IPv4: 127.0.0.1
*   Trying 127.0.0.1:80...
* Established connection to el-internal-listener-p1.apps-ocp.local (127.0.0.1 port 80) from 127.0.0.1 port 34170 
* using HTTP/1.x
> POST / HTTP/1.1
> Host: el-internal-listener-p1.apps-ocp.local
> User-Agent: curl/8.18.0
> Accept: */*
> Content-Type: application/json
> Content-Length: 92
> 
* upload completely sent off: 92 bytes
< HTTP/1.1 202 Accepted
< content-type: application/json
< date: Mon, 04 May 2026 16:04:04 GMT
< content-length: 162
< set-cookie: 101759d7115e46e889c337e10d952446=976990b496f911409694d1e74637c31a; path=/; HttpOnly
[.............]

				
			

Confirm that the automation successfully instantiated the pipeline:

				
					$  tkn pipelinerun list
NAME                        STARTED         DURATION   STATUS
automated-hello-run-ptskz   8 seconds ago   ---        Running
$ tkn pipelinerun logs automated-hello-run-ptskz 
[hello-task-reference : say-hello] Hello, World! Automation successful.
$ tkn pipelinerun list
NAME                        STARTED          DURATION   STATUS
automated-hello-run-ptskz   13 seconds ago   12s        Succeeded

				
			

This demonstrates how Tekton Triggers catch live metadata and put it to work instantly. It’s the shift from running manual commands to building a system that actually reacts to your code changes in real time.

Pipeline security and supply chain integrity

In a modern cloud-native engineering workflow, automating the build is only half the battle. Ensuring artifact security and validation compliance is a critical requirement for any production-ready pipeline.

Image scanning with Trivy

To protect supply chain integrity, we implement security “gates” using Trivy, a lightweight vulnerability scanner. Using Trivy vets images for vulnerabilities before they are promoted or deployed.

We create a dedicated security Task that scans a container image for “High” or “Critical” vulnerabilities. If the scanner detects these risks, it will exit with a non-zero code, effectively failing the pipeline and preventing the deployment of insecure code.

This task uses the Trivy image to inspect a target image and generates a failure if it finds severe vulnerabilities.

Start by defining and creating the Trivy scan task as follows.

				
					apiVersion: tekton.dev/v1
kind: Task
metadata:
  name: trivy-scan
spec:
  params:
    - name: image-to-scan
      description: Public image to scan
      default: "node:18-slim"
  steps:
    - name: scan
      image: aquasec/trivy:latest
      command: ["trivy"]
      args:
        - image
        - "--severity"
        - "HIGH,CRITICAL"
        - "--exit-code"
        - "1" 
        - $(params.image-to-scan)
				
			
				
					$ oc apply -f trivy-scan-task.yaml
task.tekton.dev/trivy-scan created

				
			

Create a pipeline as shown below. The pipeline is configured to pass a parameter down to the task and uses runAfter to restrict core workload execution to instances where the security scan passes.

				
					apiVersion: tekton.dev/v1beta1
kind: Pipeline
metadata:
  name: secured-hello-pipeline
spec:
  params:
    - name: image-to-scan
      type: string
      default: "node:18-slim"
  tasks:
    - name: security-scan
      taskRef:
        name: trivy-scan
      params:
        - name: image-to-scan
          value: $(params.image-to-scan) # Passes pipeline param to task param
    - name: hello-task
      runAfter: ["security-scan"] 
      taskRef:
        name: hello-world-task
				
			
				
					$ oc apply -f secured-pipeline.yaml
				
			

To understand how Trivy protects your environment, we can look at two specific execution scenarios.

Scenario 1: Vulnerability detected

If we scan an image with known vulnerabilities (such as “node:18-slim”), Trivy identifies critical risks and forces the task to fail.

				
					$   tkn pipeline start secured-hello-pipeline --param image-to-scan="node:18-slim" --showlog
PipelineRun started: secured-hello-pipeline-run-qq2x8
Waiting for logs to be available...
[security-scan : scan] 2026-05-04T16:53:48Z    INFO    [vulndb] Need to update DB
[security-scan : scan] 2026-05-04T16:53:48Z    INFO    [vulndb] Downloading vulnerability DB...
[security-scan : scan] 2026-05-04T16:53:48Z    INFO    [vulndb] Downloading artifact...    repo="mirror.gcr.io/aquasec/trivy-db:2"
[...........]
[security-scan : scan] ┌──────────────────────────────────────────────────────────────────────────────────┬──────────┬─────────────────┬─────────┐
[security-scan : scan] │                                      Target                                      │   Type   │ Vulnerabilities │ Secrets │
[security-scan : scan] ├──────────────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┼─────────┤
[security-scan : scan] │ node:18-slim (debian 12.11)                                                      │  debian  │       21        │    -    │
[security-scan : scan] ├────────────────────────────────────────────────────────
[............]
[security-scan : scan] node:18-slim (debian 12.11)
[security-scan : scan] ===========================
[security-scan : scan] Total: 21 (HIGH: 20, CRITICAL: 1)
[..........]
[security-scan : scan] ┌────────────────────┬────────────────┬──────────┬──────────────┬───────────────────┬────────────────────┬──────────────────────────────────────────────────────────────┐
[security-scan : scan] │      Library       │ Vulnerability  │ Severity │    Status    │ Installed Version │   Fixed Version    │                            Title                             │
[security-scan : scan] ├────────────────────┼────────────────┼──────────┼──────────────┼───────────────────┼────────────────────┼──────────────────────────────────────────────────────────────┤
[security-scan : scan] │ gpgv               │ CVE-2025-68973 │ HIGH     │ fixed        │ 2.2.40-1.1        │ 2.2.40-1.1+deb12u2 │ GnuPG: GnuPG: Information disclosure and potential arbitrary │
[..................]
failed to get logs for task security-scan : container step-scan has failed  : [{"key":"StartedAt","value":"2026-05-04T16:53:47.803Z","type":3}]

				
			

Scenario 2: Compliant image

When we use a highly secure, minimal image like the Red Hat Universal Base Image (UBI), the scanner finds no critical policy violations.

				
					$ tkn pipeline start secured-hello-pipeline --param image-to-scan="registry.access.redhat.com/ubi9/ubi-micro" --showlog
PipelineRun started: secured-hello-pipeline-run-2c5df
Waiting for logs to be available...
[security-scan : scan] 2026-05-04T17:01:36Z    INFO    [vulndb] Need to update DB
[security-scan : scan] 2026-05-04T17:01:36Z    INFO    [vulndb] Downloading vulnerability DB...
[security-scan : scan] 2026-05-04T17:01:36Z    INFO    [vulndb] Downloading artifact...    repo="mirror.gcr.io/aquasec/trivy-db:2"

[............]
[security-scan : scan] 2026-05-04T17:03:18Z    INFO    [vuln] Vulnerability scanning is enabled
[security-scan : scan] 2026-05-04T17:03:18Z    INFO    [secret] Secret scanning is enabled
[security-scan : scan] 2026-05-04T17:03:18Z    INFO    [secret] If your scanning is slow, please try '--scanners vuln' to disable secret scanning
[security-scan : scan] 2026-05-04T17:03:18Z    INFO    [secret] Please see https://trivy.dev/docs/v0.70/guide/scanner/secret#recommendation for faster secret detection
[security-scan : scan] 2026-05-04T17:03:30Z    INFO    Detected OS    family="redhat" version="9.7"
[security-scan : scan] 2026-05-04T17:03:30Z    INFO    [redhat] Detecting RHEL/CentOS vulnerabilities...    os_version="9" pkg_num=20
[security-scan : scan] 2026-05-04T17:03:30Z    INFO    Number of language-specific files    num=0
[security-scan : scan] 2026-05-04T17:03:30Z    WARN    Using severities from other vendors for some vulnerabilities. Read https://trivy.dev/docs/v0.70/guide/scanner/vulnerability#severity-selection for details.
[security-scan : scan] 
[security-scan : scan] Report Summary
[security-scan : scan] 
[security-scan : scan] ┌────────────────────────────────────────────────────────┬────────┬─────────────────┬─────────┐
[security-scan : scan] │                         Target                         │  Type  │ Vulnerabilities │ Secrets │
[security-scan : scan] ├────────────────────────────────────────────────────────┼────────┼─────────────────┼─────────┤
[security-scan : scan] │ registry.access.redhat.com/ubi9/ubi-micro (redhat 9.7) │ redhat │        0        │    -    │
[security-scan : scan] └────────────────────────────────────────────────────────┴────────┴─────────────────┴─────────┘
[security-scan : scan] Legend:
[security-scan : scan] - '-': Not scanned
[security-scan : scan] - '0': Clean (no security findings detected)
[security-scan : scan] 
[hello-task : say-hello] Hello, World! Automation successful.

				
			

Persisting Audit Metadata with Tekton Results

To store, build, and persist security metadata, Tekton provides the Results API. Unlike logs, which can be voluminous and ephemeral, Results allow you to store small, structured pieces of data, like a “COMPLIANT” status or an image digest, that remain attached to the TaskRun or PipelineRun long after the execution pod has been deleted.

Define the results object and add a step to write the data to the automatically generated result path.

				
					apiVersion: tekton.dev/v1
kind: Task
metadata:
  name: trivy-scan
spec:
  params:
    - name: image-to-scan
      description: Public image to scan
      default: "node:18-slim"
      type: string
  results:
    - name: scan-status
      description: Indicates if the scan passed or failed.
      type: string
  steps:
    - name: scan
      image: aquasec/trivy:latest
      command: ["trivy"]
      args:
        - image
        - "--severity"
        - "HIGH,CRITICAL"
        - "--exit-code"
        - "1" 
        - $(params.image-to-scan)
    - name: write-result
      image: registry.access.redhat.com/ubi9/ubi-micro
      script: |
        #!/usr/bin/env bash
        # Only reached if the previous 'scan' step succeeds
        echo "COMPLIANT" > $(results.scan-status.path)

				
			

Create the updated task and execute the same pipeline again. You can now retrieve the structured audit metadata directly.

				
					$ tkn taskrun describe --last
Name:              secured-hello-pipeline-run-v9q4l-hello-task
Namespace:         p1
Task Ref:          hello-world-task
Service Account:   pipeline
Timeout:           1h0m0s
Labels:
 app.kubernetes.io/managed-by=tekton-pipelines
 tekton.dev/memberOf=tasks
 tekton.dev/pipeline=secured-hello-pipeline
 tekton.dev/pipelineRun=secured-hello-pipeline-run-v9q4l
 tekton.dev/pipelineRunUID=fb95155c-6e43-4769-a395-8c79199e18f6
 tekton.dev/pipelineTask=hello-task
 tekton.dev/task=hello-world-task
Annotations:
 chains.tekton.dev/signed=true
 pipeline.tekton.dev/release=52a824a1a459db1461390074ce2f1ac438aa3c32
 results.tekton.dev/childReadyForDeletion=true
 results.tekton.dev/record=p1/results/fb95155c-6e43-4769-a395-8c79199e18f6/records/a901fcab-b643-45df-b6bb-f32b1817cdc4
 results.tekton.dev/result=p1/results/fb95155c-6e43-4769-a395-8c79199e18f6
 results.tekton.dev/stored=true

🌡️  Status

STARTED          DURATION    STATUS
27 seconds ago   18s         Succeeded

🦶 Steps

 NAME          STATUS
 ∙ say-hello   Completed

				
			

In enterprise environments, you’ll likely need to pull from private registries or clone from private Git repositories. You can handle authentication without changing a single line of your YAML by linking Kubernetes Secrets to your ServiceAccount.

You can define your credentials in a standard Docker registry and tell Tekton which registry this secret belongs to and attach it to the pipeline ServiceAccount.

				
					$ oc create secret docker-registry private-registry-creds \
    --docker-server=registry.redhat.io \
    --docker-username=<your-username> \
    --docker-password=<your-password>
$ oc create sa demo-pipeline-sa
$ oc annotate secret private-registry-creds "tekton.dev/docker-0=quay.io"
$ oc secrets link demo-pipeline-sa private-registry-creds --for=pull,mount
$ tkn pipeline start secured-hello-pipeline \
    --param image-to-scan="registry.redhat.io/ubi9/ubi-micro" \
    --serviceaccount demo-pipeline-sa \
    --showlog

				
			

Ensuring Pipeline Resilience and Recovery

Once your pipeline handles security gates and triggers, the final step is to protect that logic. The Tekton objects and Custom Resource Definitions (CRDs) reside directly in etcd, which means they, like all other API objects, are also vulnerable to cluster-wide disruptions. A botched cluster upgrade or an accidental namespace deletion can instantly wipe out the entire CI/CD delivery framework.

For this purpose, you can consider a platform like Trilio. Trilio checkpoints the exact automation state by capturing the declarative pipeline objects alongside the live application state. This means if you need to migrate your workloads or recover from an outage, your entire declarative pipeline, not just the resulting container, is restored in its last known good state.

Recommended practices for OpenShift Pipelines

Based on the core components and workflows implemented in this guide, adhere to the following practices for enterprise CI/CD orchestration:

  • Standardize reusable tasks: Build a library of modular tasks, like your vulnerability scanner or build logic, so you can drop them into any pipeline without reinventing the wheel every time.
  • Implement Least-Privilege ServiceAccounts: Default pipeline configurations often rely on over-privileged service accounts, creating unnecessary security risks. For production pipelines, create dedicated service accounts bound to specific, narrow roles. This restricts permissions to only the exact tasks required.
  • Implement event-driven automation: Manual tkn commands are fine for testing, but real-world workflows need Tekton Triggers. This lets your cluster watch for Git pushes or PRs and trigger the automation.
  • Keep secrets out of your YAML: Don’t hardcode credentials or API keys. Stick them in Kubernetes Secrets and tie them to your Pipeline’s ServiceAccount so they stay protected and out of your source code.
  • Make security a “hard stop”: Treat your scan tasks like a real gate. If a tool like Trivy finds a critical bug, the pipeline should fail immediately, stopping insecure code from ever reaching your environment.
  • Persist audit metadata: Pod logs disappear. Use the Results API to save scan statuses and image IDs directly to the cluster, so you actually have a permanent record when audit time rolls around.

Learn How To Best Backup & Restore Virtual Machines Running on OpenShift

Conclusion

Applying legacy CI/CD architectures to cloud-native platforms such as Kubernetes and OpenShift often imposes several constraints. The reason is that they were fundamentally built around static build nodes and a centralized controller. OpenShift Pipelines solves this problem by dropping the central server entirely. 

OpenShift Pipelines removes the chronic resource bottlenecks and compute waste during peak deployment hours that plague older systems by eliminating the reliance on a single, persistent controller server. OpenShift Pipelines scales dynamically with the cluster, executing each build step within ephemeral containers that consume resources only when active.

With the practical framework for modular tasks and automated triggers established in this guide, a production-ready, declarative delivery system is now fully within reach. To scale this architecture further, the next logical step is adopting pipelines as code. This allows teams to store repository-level pipeline definitions directly alongside the application source code, driving the entire execution lifecycle straight from Git webhooks.

Table Of Contents

Like This Article?

Subscribe to our LinkedIn Newsletter to receive more educational content

Privacy Overview

This website uses cookies so that we can provide you with the best user experience possible. Cookie information is stored in your browser and performs functions such as recognising you when you return to our website and helping our team to understand which sections of the website you find most interesting and useful.