Architecting OpenShift Pipelines as Code for Enterprise Monorepos: A Technical Guide
Introduction
Architecting OpenShift Pipelines as Code for Enterprise Monorepos: A Technical Guide

Introduction
The enterprise software landscape has reached an inflection point. After years of building isolated microservices in separate repositories, organizations are rediscovering the operational advantages of consolidation. Monorepos — unified Git repositories housing multiple applications, shared libraries, and microservices — offer atomic commits, simplified dependency management, and unparalleled cross-team visibility.
But this architectural consolidation comes at a cost that most teams underestimate until they’re drowning in it: CI/CD complexity that legacy polyrepo-designed systems simply cannot handle. When a single commit can touch fifty services, traditional pipeline orchestrators break. They trigger everything, burn compute budgets, exhaust API rate limits, and paralyze developer velocity with hours of unnecessary builds.
I’ve architected OpenShift Pipelines as Code (PaC) solutions for enterprises managing thousands of builds daily across massive monorepos. What I’m going to share with you is the definitive technical framework for making this work — not as a theoretical exercise, but as a production-ready architecture that scales.
The Core Challenge: Build Storms and Their Hidden Cost
Traditional CI/CD systems were designed for a polyrepo world. One repository, one application, one pipeline definition. This model worked beautifully when organizations had fifty repositories for fifty services.
Then came monorepos.
In a monorepo environment, a single push to the main branch can contain changes spanning a dozen microservices, shared utility libraries, Terraform infrastructure definitions, and yes — documentation updates. Legacy CI systems respond to this by executing their global pipeline for every commit, regardless of what actually changed.
The symptom is unmistakable: a typo in docs/README.md triggers the recompilation, testing, and container image generation for all fifty services in the repository. I call this a build storm — and in high-velocity enterprises, build storms can consume thousands of compute-minutes per day, drive infrastructure costs through the roof, and exhaust Git provider API rate limits to the point where legitimate builds start failing.
The root cause isn’t the monorepo structure. It’s the CI/CD orchestration layer’s inability to determine what actually changed before instantiating Kubernetes resources.
OpenShift Pipelines as Code: The Kubernetes-Native Answer
The industry responded to polyrepo CI/CD limitations by shifting toward Kubernetes-native orchestrators, specifically the upstream Tekton project. Tekton defines declarative pipelines as Kubernetes Custom Resource Definitions (CRDs), orchestrating pipeline stages as discrete Pods executing within the cluster. This model offers unparalleled scalability and container-native execution.
However, manual submission of PipelineRun manifests to the OpenShift API server creates operational friction that enterprises don’t want to manage. This is where Pipelines as Code (PaC) becomes essential.
PaC brings GitOps methodology directly to Tekton. Development teams define CI/CD configurations using standard Tekton PipelineRuns and Tasks stored in YAML files alongside their application source code. The system operates through a webhook-driven architecture:
- Webhook interception — PaC intercepts webhooks from Git providers (GitHub, GitLab, Bitbucket Cloud)
- Configuration discovery — The controller scans the repository’s
.tekton/directory recursively - Annotation evaluation — PipelineRuns are evaluated against webhook payloads
- Pipeline execution — Matching PipelineRuns are submitted to the OpenShift cluster
- Status reporting — Execution results are reported back as Git PR checks or commit statuses
The key insight: pipeline definitions live alongside the source code they build. This isn’t just a technical decision — it’s a cultural one that shifts CI/CD ownership to application teams while maintaining enterprise governance.
Directory Layout: The Foundation of Monorepo CI/CD
The structural organization of pipeline definitions within a monorepo directly dictates maintainability, visibility, and execution efficiency. This is where most enterprises make their first critical architectural mistake.
When evaluating how to structure the .tekton/ directory in a monorepo, three patterns emerge:
Pattern 1: Application-Specific Sub-Folders (Recommended)
Organize the central .tekton/ directory into discrete sub-directories representing each application:
.tekton/
app-a/
pull-request.yaml
push.yaml
app-b/
pull-request.yaml
push.yaml
backend-api/
pull-request.yaml
push.yaml
release.yaml
This pattern natively aligns with the PaC controller’s recursive directory scanning capabilities. It isolates CI/CD triggering logic per application while maintaining a centralized discovery namespace. When a webhook arrives, the controller discovers and evaluates every PipelineRun across the entire monorepo, determining which applications need execution based on changed file paths.
Pattern 2: Centralized Shared Pipeline (Antipattern)
Using a single orchestrator pipeline (e.g., .tekton/pipelines/monorepo-orchestrator.yaml) that executes globally and determines which internal applications require building.
This approach forces the pipeline to dynamically assess build targets using complex imperative shell scripting within the Tekton Pod. It violates the declarative nature of the framework, heavily increases computational overhead, and makes debugging nearly impossible. Avoid this pattern.
Pattern 3: Decentralized .tekton/ Directories (Unsupported)
Embedding separate .tekton/ directories at the root of every application sub-folder (e.g., app-a/.tekton/, app-b/.tekton/).
The PaC webhook interceptor is hardcoded to scan the .tekton/ directory located strictly at the repository root. It does not parse the entire Git tree for arbitrarily placed .tekton/ folders. This pattern will not work.
Remote Pipelines: Solving Configuration Sprawl
The application-specific sub-folder pattern introduces a legitimate concern: if your monorepo contains fifty microservices, all utilizing identical Maven build processes, duplicating the complete Tekton Pipeline YAML across fifty sub-directories creates unmanageable maintenance burden.
OpenShift Pipelines as Code resolves this through Remote Pipeline referencing. Using the pipelinesascode.tekton.dev/pipeline annotation, application teams can reference centralized Pipeline templates without duplicating operational logic:
# .tekton/app-a/pull-request.yaml
apiVersion: tekton.dev/v1
kind: PipelineRun
metadata:
name: app-a-pr
annotations:
pipelinesascode.tekton.dev/pipeline: "shared/templates/java-build-pipeline.yaml"
pipelinesascode.tekton.dev/on-event: "[pull_request]"
pipelinesascode.tekton.dev/on-path-change: "[apps/app-a/**, libs/common-utils/**]"
The annotation supports three resolution pathways:
- In-repository paths — A relative file path inside the current repository (e.g.,
shared/templates/java-build-pipeline.yaml). This is optimal for monorepos because shared templates reside in the same repository as applications, ensuring that modifications to the template and the applications can occur atomically within the same pull request. - Remote URLs — Pipeline fetched directly from a raw YAML URL on any Git provider.
- Artifact Hubs and Custom Catalogs — Pipelines referenced by name from the public Artifact Hub registry or private administrator-configured catalogs.
The PaC resolver enforces strict precedence rules when tasks or pipelines share identical nomenclature. Remote pipelines referenced through pipelineRef are prioritized from PipelineRun annotations first, followed by pipelines discovered within .tekton/ subdirectories.
Task Overriding for Edge Cases
What happens when an application needs to deviate from the shared template for one specific task? PaC provides the pipelinesascode.tekton.dev/task annotation for this exact scenario:
pipelinesascode.tekton.dev/task: "./custom-git-clone-task.yaml"
When this annotation is present, the PaC resolver prioritizes the local task definition over the task embedded within the remote pipeline template. This provides unparalleled flexibility within a tightly controlled governance model.
Technology Preview Note: Task overriding only applies to
taskRefreferences, not embeddedtaskSpecdefinitions.
Change Detection: Preventing Build Storms
The most complex technical hurdle in monorepo CI/CD is implementing precise change detection. When a developer pushes to main, the platform must determine exactly which sub-projects, libraries, and applications were affected by the specific commit — before creating any Kubernetes resources.
The Antipattern: In-Pipeline Git Diffing
A legacy approach, frequently migrated from scripting-heavy orchestrators like Jenkins, involves triggering a generic global PipelineRun for every commit. The first Task in this pipeline spins up a container, clones the repository, and executes git diff --name-only HEAD~1 to determine what changed.
This methodology is deeply inefficient within a Kubernetes-native ecosystem. Executing this logic requires the OpenShift API server to process and persist the PipelineRun Custom Resource, the Tekton controller to schedule a Pod on a worker node, the container runtime to pull necessary utility images, and the node to allocate CPU and memory resources — all merely to determine that no actual build action is required.
In highly active monorepos with hundreds of daily commits, this pattern causes control plane degradation, API rate limit exhaustion, and infrastructure waste that can cripple your CI/CD system.
The Recommended Pattern: Path-Based Annotations
PaC’s webhook interceptor evaluates changes against PipelineRun annotations before any Kubernetes resources are created. The foundational mechanism is the pipelinesascode.tekton.dev/on-path-change annotation:
# .tekton/app-a/pull-request.yaml
apiVersion: tekton.dev/v1
kind: PipelineRun
metadata:
name: app-a-pr-validation
annotations:
pipelinesascode.tekton.dev/on-event: "[pull_request]"
pipelinesascode.tekton.dev/on-target-branch: "[main]"
pipelinesascode.tekton.dev/on-path-change: "[apps/app-a/**, libs/common-utils/**]"
When a pull request modifies files within apps/app-a/src/, the annotation evaluates to true, and the controller submits the PipelineRun to the OpenShift API server. If the pull request strictly modifies files in apps/app-b/src/, the controller quietly ignores this PipelineRun definition without creating any resources.
The complementary pipelinesascode.tekton.dev/on-path-change-ignore annotation excludes events from matching when only specified paths are changed:
pipelinesascode.tekton.dev/on-path-change-ignore: "[docs/**, .github/**, **/*.md]"
This combination ensures that documentation updates, GitHub Actions workflow changes, and other non-functional modifications never trigger CI execution — while ensuring that functional changes to either the application or its shared dependencies always trigger appropriate validation.
Fine-Grained Control with CEL Expressions
While standard path annotations handle the majority of monorepo isolation scenarios, enterprise architectures frequently demand complex conditional triggering logic that extends far beyond simple glob matching. For these advanced scenarios, PaC natively integrates the Common Expression Language (CEL).
When pipelinesascode.tekton.dev/on-cel-expression is declared, it establishes absolute priority over all standard matching annotations in the same file. The controller relies entirely on the CEL expression as the sole source of truth for trigger evaluation.
CEL expressions expose a rich set of variables derived from the Git provider’s webhook payload:
event— The event type (push, pull_request)event_type— Provider-specific event headerstarget_branch,source_branch— Branch informationevent_title— Commit title or PR titlefiles.all,files.added,files.deleted,files.modified,files.renamed— Granular file change properties
Infrastructure-as-Code Triggering
For compliance and security requirements, you might need to trigger specialized pipelines only when infrastructure-as-code manifests are modified:
pipelinesascode.tekton.dev/on-cel-expression: |
files.modified.exists(x, x.matches('^infra/terraform/.*\\.tf$')) ||
files.added.exists(x, x.matches('^infra/terraform/.*\\.tf$'))
Excluding Non-Code Changes
To ensure automated integration tests execute only when actual application logic changes occur — preventing resource waste on documentation-only pull requests:
pipelinesascode.tekton.dev/on-cel-expression: |
event == "pull_request" && target_branch == "main" &&
!files.all.all(x, x.matches('^docs/') || x.matches('\\.md$') || x.matches('(\\.gitignore|OWNERS|PROJECT|LICENSE)$'))
Critical Note: When constructing CEL expressions, use double backslashes (
\\) to escape special characters within the YAML string context. The.pathChanged()suffix function is limited to GitHub and GitLab providers.
Event Handling: The Segregated PipelineRuns Pattern
A single microservice within a monorepo requires drastically different pipeline executions depending on the Git operation context. A pull request mandates unit testing, static application security testing (SAST), and ephemeral preview environments. A push to main requires container image building, artifact signing, and staging deployment. A tag release mandates production deployment, vulnerability scanning, and SBOM generation.
The architectural question: should teams use separate PipelineRuns for each event type, or a single generic PipelineRun with conditional routing?
The answer is unambiguous: define separate PipelineRuns for each event type.
This isn’t a stylistic preference — it’s a fundamental architectural principle. PaC was engineered as a decentralized, annotation-based routing engine. When a webhook payload arrives, the controller evaluates the annotations of every YAML file discovered in the recursive .tekton/ directory. Using a single generic PipelineRun for all events actively bypasses the controller's highly optimized routing efficiency, forcing conditional logic downstream into Tekton execution.
Attempting to build a monolithic pipeline with conditional routing via Tekton WhenExpressions results in bloated, unmaintainable Pipeline Custom Resources that violate every principle of clean code. As your application's lifecycle matures, a generic pipeline must accommodate increasingly complex parameter matrices to track whether it is executing in a pull request context, main-branch integration context, or release context.
Segregated PipelineRuns uphold the principle of single responsibility:
# .tekton/app-a/pull-request.yaml
name: app-a-pr
annotations:
pipelinesascode.tekton.dev/on-event: "[pull_request]"
# .tekton/app-a/push.yaml
name: app-a-push
annotations:
pipelinesascode.tekton.dev/on-event: "[push]"
# .tekton/app-a/release.yaml
name: app-a-release
annotations:
pipelinesascode.tekton.dev/on-event: "[tag]"
When a pipeline fails, the resulting execution trace strictly reflects the relevant context. Debugging a failed production deployment no longer requires parsing through logs of skipped unit test tasks and bypassed preview-environment provisioning steps.
Cross-Application Dependencies
Monorepo architectures derive significant value from shared libraries. When a developer submits a pull request updating a shared utility library in libs/common-utils/, that modification must trigger PR validation pipelines for both app-a and app-b if both applications depend on that shared logic.
PaC handles this elegantly through the path-based annotation system. By utilizing segregated PipelineRuns, architects can ensure cross-application triggering without modifying underlying build logic:
# .tekton/app-a/pull-request.yaml
apiVersion: tekton.dev/v1
kind: PipelineRun
metadata:
name: app-a-pr-validation
annotations:
pipelinesascode.tekton.dev/on-event: "[pull_request]"
pipelinesascode.tekton.dev/on-target-branch: "[main]"
pipelinesascode.tekton.dev/on-path-change: "[apps/app-a/**, libs/common-utils/**]"
When the webhook fires indicating a change to libs/common-utils/, the PaC controller evaluates the payload, identifies the path change, and automatically triggers PR validation pipelines for every application monitoring that shared library. Breaking changes in core libraries are instantly caught by the integration tests of dependent applications — this is the primary benefit of monorepo architecture made operational through proper CI/CD design.
Webhook Security: The /ok-to-test Gate
Open monorepos handling contributions from external users face significant security challenges. The pull_request_target event type in Git providers presents inherent risks — it executes code within the target repository's elevated context, potentially exposing cluster secrets, container registry credentials, and deployment tokens to malicious actors.
PaC integrates an automated label-based authorization mechanism that eliminates these attack vectors. By default, PaC will not execute a PipelineRun for a pull request submitted by an external user. Instead, it pauses pipeline generation and requires a repository administrator to manually comment /ok-to-test on the pull request.
This ChatOps integration serves as a mandatory security gate. A human administrator must visually review code changes to ensure they don’t contain malicious payload extraction scripts before issuing the authorization command.
Recent OpenShift Pipelines operator updates have refined this process significantly. The controller now ensures the GitHub Checks API properly resolves and clears pending check-run states when /ok-to-test is utilized, preventing unapproved pipelines from appearing permanently stuck in the developer's interface. The /retest command allows developers to re-trigger only failed pipelines, conserving compute resources in massive monorepos.
The Konflux CI Paradigm: Red Hat’s Reference Architecture
For enterprise-grade monorepo management involving complex interdependencies and strict compliance mandates, Red Hat’s Konflux CI represents the definitive reference architecture. Konflux is an open-source, highly opinionated software supply chain platform that natively inherits the pattern of defining Tekton build pipelines in Git via Pipelines as Code.
Konflux solves the monorepo challenge through rigorous structural abstractions:
- Application Custom Resource — Acts as a logical grouping for a cohesive software product
- Component Custom Resource — Describes the properties, source location, and dependencies of a specific OCI container artifact or buildable entity
In a monorepo context, a single Git repository houses the source code for multiple distinct Konflux Components. When Konflux monitors the repository via PaC, it leverages the integration to map inbound Git events directly to specific Component pipelines — almost completely eliminating the need for individual developers to manually configure complex .tekton/ directory routing.
Supply Chain Security Built-In
Every Component built through the Konflux framework utilizes Tekton Chains and Tekton Results to capture provenance and generate cryptographically signed attestations. This provides SLSA Level 3 compliance intrinsically — verification of every application inside the monorepo without burdening individual application teams with signature generation, key management, and artifact attestation complexity.
MintMaker: Automated Dependency Management
A persistent operational challenge in monorepo management is tracking and updating external dependencies across dozens of nested applications. Konflux introduces MintMaker — a declarative system for triggering dependency scanning and automated updates.
MintMaker operates by introducing a DependencyUpdateCheck Custom Resource. When created, MintMaker comprehensively examines all Components within the Konflux Application for outdated dependencies, dynamically creates Tekton PipelineRun instances to execute Renovate scans, and automatically generates pull requests containing dependency updates.
By centralizing dependency management at the Application level rather than forcing it upon individual Component teams, MintMaker ensures that monorepos don’t suffer from severe dependency drift. The automated PR generation → PaC validation pipeline → approval merge cycle creates a closed-loop maintenance system.
Post-Build Orchestration: Snapshots
Konflux enforces a “build once, release multiple times” mentality perfectly suited for monorepos. Once individual components are built via PaC triggers, Konflux generates a Snapshot Custom Resource — an immutable set of component references capturing the exact state of the entire monorepo ecosystem at a specific point in time.
This Snapshot is subjected to an IntegrationTestScenario (ITS) — a specialized Tekton Pipeline defining a suite of tests to run against the entire snapshot collectively. Only if the Snapshot passes the IntegrationTestScenario and adheres to the EnterpriseContractPolicy is it passed to a ReleasePlan for deployment.
This separation of component building (via Pipelines as Code) from systemic integration testing (via Snapshots) represents the pinnacle of monorepo CI/CD architecture.
Scaling the Control Plane
Deploying PaC in a monorepo context requires proactive tuning of the OpenShift Pipelines operator to handle the significantly increased computational load. Monorepos generate substantially higher webhook traffic, API interactions, and concurrent PipelineRun executions than standard polyrepo architectures.
Log Scanning and Error Detection Limits
PaC’s ability to parse Tekton Task container logs to extract execution failure reasons and post them as comments on pull requests can consume significant memory in monorepo contexts. By default, PaC restricts this operation to the last 50 lines of container logs.
Cluster administrators can increase this value via the error-detection-max-number-of-lines field within the PaC ConfigMap, or set it to -1 for unlimited scanning. However, in high-velocity monorepo environments, this is strongly discouraged.
Boundless log scanning across dozens of concurrent failing applications will drastically inflate the memory footprint of the PaC watcher process, potentially leading to Out-Of-Memory evictions of the controller by the Kubernetes scheduler — bringing the entire CI/CD infrastructure to a halt.
The architectural recommendation: Maintain the strict 50-line limit and mandate that application developers ensure their testing frameworks emit concise, standardized error summaries to stderr at task termination.
Memory Optimization
Recent iterations of PaC have implemented aggressive memory reduction techniques. Specialized TransformFuncs strip large unnecessary Kubernetes metadata fields — such as ManagedFields, extensive Annotations, deep Status objects, and complete Spec definitions — before objects enter the informer cache.
For a monorepo processing thousands of PipelineRuns daily, this architectural tuning prevents the PaC controller from exhausting node memory and drastically reduces the serialization burden on the OpenShift etcd key-value store.
Distributed Tracing
Diagnosing latency or dropped webhooks in high-volume monorepos requires advanced observability. Cluster administrators should enable distributed tracing via the pipelines-as-code-config-observability ConfigMap.
When enabled, PaC emits OpenTelemetry trace spans detailing webhook event processing times and the complete PipelineRun lifecycle timing with W3C trace context propagation. This allows platform engineering teams to identify exactly where processing delays occur — whether in Git provider API communication, CEL expression evaluation, or Tekton pod scheduling.
Ecosystem Comparison
For context on where PaC fits in the broader CI/CD landscape, it’s worth comparing to alternatives:
Armory’s Spinnaker implements Pipelines-as-Code via Dinghy. In a monorepo setup, Dinghy supports multiple Spinnaker applications under the same Git repository by placing a dinghyfile in each application's directory, maintaining a dependency graph and processing changes across the repository.
OpenShift Pipelines as Code improves upon this paradigm by centralizing the discovery namespace to the .tekton/ directory. This centralization prevents the CI/CD controller from indiscriminately parsing the entire repository tree — a process that can be highly resource-intensive in massive monorepos. By utilizing recursive discovery exclusively within .tekton/, PaC strikes an optimal balance between application-specific isolation and centralized controller efficiency.
The Seven Foundational Principles
Based on extensive production experience and analysis of Red Hat’s internal reference architectures, the recommended setup for Pipelines as Code in monorepos is defined by these foundational principles:
- Embrace Recursive Directory Layouts — Reject monolithic pipeline designs. Leverage the controller’s recursive discovery capabilities to maintain Application-Specific Sub-Folders (
.tekton/app-a/,.tekton/app-b/) within a unified root directory. - Utilize Remote Pipelines to Enforce Standardization — Prevent configuration drift by utilizing the
pipelinesascode.tekton.dev/pipelineannotation to reference centralized Pipeline definitions stored in a shared directory. Override specific tasks locally only when absolutely necessitated by edge-case requirements. - Shift Change Detection to the Webhook Payload Layer — Abandon legacy in-pipeline git diff scripts. Rely on the PaC webhook interceptor to determine execution validity before any Kubernetes resources are instantiated.
- Implement Segregated PipelineRuns per Event Context — Reject generic monolithic PipelineRuns burdened with complex conditional logic. Architect segregated PipelineRuns explicitly tailored per event type.
- Enforce Strict Security Boundaries — Protect the monorepo’s shared cluster secrets by embracing the
/ok-to-testChatOps authorization workflow. Never rely onpull_request_targettriggers for untrusted contributors. - Adopt the Konflux CI Paradigm for Massive Scale — For enterprise-grade monorepo management involving complex interdependencies and strict compliance mandates, utilize Konflux’s Application and Component Custom Resources, integrated with MintMaker for automated dependency management and Tekton Chains for artifact attestation.
- Optimize the Control Plane — Strictly adhere to the 50-line error-detection limit. Enable OpenTelemetry distributed tracing and informer cache memory reduction to ensure the PaC operator handles high monorepo event volumes.
Conclusion
The transition from isolated repositories to a consolidated monorepo architecture demands fundamental reevaluation of CI/CD orchestration. Traditional pipeline methodologies inevitably buckle under the weight of monorepo interdependencies, leading to infrastructure exhaustion and developer frustration.
OpenShift Pipelines, empowered by the declarative GitOps capabilities of Pipelines as Code, provides the native mechanisms required to master this complexity. By rigorously adhering to these declarative, Kubernetes-native patterns, organizations can transform a potential developmental bottleneck into a highly performant, exceptionally secure, and fully automated software delivery engine.
The monorepo isn’t going away. The question is whether your CI/CD infrastructure is ready to handle it at enterprise scale.
References and Further Reading:
메타데이터
- post_id
- c05bffc53c86
- slug
- architecting-openshift-pipelines-as-code-for-enterprise-monorepos-a-technical-guide-c05bffc53c86
- url
- https://medium.com/@tcij1013/architecting-openshift-pipelines-as-code-for-enterprise-monorepos-a-technical-guide-c05bffc53c86
- canonical_url
- https://medium.com/@tcij1013/architecting-openshift-pipelines-as-code-for-enterprise-monorepos-a-technical-guide-c05bffc53c86
- author_url
- https://medium.com/@tcij1013
- status
- ok
- fetched_at
- 2026-07-10 09:52:19