Path A — Jenkins / Trivy / Nexus

The Jenkins-on-a-VM build path: how Jenkins pulls source from GitLab, builds with podman/buildah on the agent, scans with the shared Trivy VM, pushes to Nexus app-registry, uploads evidence to MinIO, and ends by writing a digest patch MR into the app GitOps repo. Includes a side-by-side comparison against Path B.

This page is the operator and developer reference for Path A — the Jenkins-based build path. It is the first build path to land in the lab (Jenkins is already live; Tekton is staging). It is the OCP counterpart to the existing docker-runtime-vm Jenkins chain documented in connection-details/jenkins.md: same Jenkins service, same agent, same Trivy / Nexus / MinIO infrastructure, same operator credentials. The only difference is the final step: docker-runtime-vm deploys via SSH to a VM; OCP deploys via a Git commit that Argo picks up.

Tracked under:

  • DEV-OCP-3A.1 (#187) — extend Jenkins build jobs to target OCP.
  • DEV-OCP-3A.2 (#188) — Jenkins → GitOps overlay digest-patch step.

Path A vs Path B — side-by-side

Before diving into Path A, here is the structural comparison against Path B (which Section 03-build-paths/02 covers in detail). Both paths land on the same digest patch in the same overlay file; the difference is where the build runs and which image registry it pushes to.

AspectPath A — JenkinsPath B — Tekton
Build hostjenkins-agent-0 VM, label developer-buildPer-build pod in openshift-pipelines namespace on spoke-dc-v6
TriggerGitLab push webhook → Jenkins Git plugin notifyCommit URLGitLab push webhook → Tekton EventListener Route
Webhook secret custodyJenkins credential store (git-notifycommit-<project>-token)Vault secret/ocp/spoke-dc-v6/tekton/gitlab-webhook → ESO Secret
Build toolpodman 4.9.3, buildah 1.33.7buildah Task in OpenShift Pipelines
Scantrivy --server against the Trivy VMtrivy --server against the same Trivy VM
Image registry (push)app-registry.apps.sub.comptech-lab.com (Nexus)quay.apps.sub.comptech-lab.com (Quay) or Nexus fallback
Registry push credentialJenkins credential store (nexus-jenkinsbot)Vault secret/apps/<division>/<app>/ci/quay-robot → ESO Secret per tenant
Evidence targetMinIO bucket developer-ci-evidenceSame bucket, same prefix shape, same keys
Overlay patch stepupdate-overlay-digest.sh invoked on the agent, push via PATupdate-overlay-digest Tekton Task, push via ESO-materialised PAT
Argo seesa new commit on apps/<team>/<app>/overlays/dev/kustomization.yamlsame
Migration A → Bsupported; both paths terminate at the same digest patchreverse migration (B → A) is not supported

The defining symmetry: Argo CD reads apps/<team>/<app>/overlays/dev/kustomization.yaml and cannot tell which path produced the commit. That is the design intent — both paths are pluggable behind the same GitOps contract.

Path A architecture

flowchart LR
  dev[Developer] -->|git push main| gitlab[(GitLab CE<br/>gitlab.apps.sub.comptech-lab.com)]
  gitlab -->|push webhook<br/>notifyCommit| jenkins[Jenkins 2.555.1<br/>jenkins-0.sub]
  jenkins -->|schedules| agent[Build Agent<br/>jenkins-agent-0<br/>label: developer-build]
  agent -->|podman pull| nexus_pull[(docker-group.*<br/>base images)]
  agent -->|trivy --server| trivy[(Trivy VM)]
  agent -->|podman push| nexus_push[(app-registry.*<br/>tenant images)]
  agent -->|mc cp| minio[(MinIO<br/>developer-ci-evidence)]
  agent -->|git push| monorepo[(app-monorepo<br/>apps/&lt;team&gt;/&lt;app&gt;/overlays/dev/)]
  monorepo -->|hub Argo reads| argo[OpenShift GitOps<br/>on hub-dc-v6]
  argo -->|pull model<br/>ACM addon| spoke[Spoke Argo<br/>on target cluster]
  spoke -->|kustomize apply| ocp[OCP Deployment<br/>apps-&lt;division&gt;-&lt;team&gt;-&lt;env&gt; ns]

The build agent is a single, long-running VM with warm caches:

  • Maven local repo (~/.m2/repository) — saves 30-90 seconds per Liberty/Spring build.
  • Buildah layer cache — saves UBI base layer downloads.
  • /opt/ops-workspace/scripts/ checkout — provides update-overlay-digest.sh and promote.sh.

For builds that benefit from the warm cache (Liberty + Maven, Spring Boot + Maven, Node.js + npm), Path A is faster cold-to-cold than a fresh Tekton pod. For builds that elastically scale across many cluster nodes, Path B’s per-build pod model wins. The choice is captured in the decision matrix.

Tooling on jenkins-agent-0

ToolVersionUsed for
OpenJDK21Maven, language build
Mavenrecent stableLiberty / Spring builds
Node.js / npmrecent stableNode.js builds
Podman4.9.3Container image build (rootless), podman login, podman push
Buildah1.33.7Lower-level image build (when invoked directly)
Skopeo1.13.3Registry inspection (skopeo inspect to verify pushed digest)
Trivy0.70.0Scan client, server mode against the Trivy VM
MinIO client (mc)recent stableEvidence upload
ops-workspace/scripts/update-overlay-digest.shversioned by repo HEADOverlay patch
ops-workspace/scripts/promote.shversioned by repo HEADDev → stg → prd promotion

Skopeo presence was rechecked on 2026-05-09 and is at /usr/bin/skopeo. Trivy server URL is fronted by HAProxy on the Trivy VM.

Credentials

All credentials referenced below live in the Jenkins credential store at https://jenkins.apps.sub.comptech-lab.com/credentials/. Local custody copies live under opp-full-plat/secrets/jenkins/ (Git-ignored). Do not paste values into this file, into tickets, or into chat.

Credential IDTypeOwnerScopeUsed for
nexus-jenkinsbotUsername + passwordplatform-adminsystemPull base images from docker-group.*, push app images to app-registry.*
trivy-server-tokenSecret textplatform-adminsystemTrivy server auth for the enforced CRITICAL gate
minio-developer-ci-evidenceUsername + passwordplatform-adminsystemScoped writer for developer-ci-evidence bucket
gitlab-mavenbot-patUsername + passwordplatform-adminsystemGitLab PAT with write_repository scope on the app monorepo; pushes the overlay digest patch
git-notifycommit-<project>-tokenSecret textper projectper jobGates the GitLab push webhook into Jenkins for a given tenant project

gitlab-mavenbot-pat may be replaced by a dedicated apps-overlay-bot PAT scoped only to the app monorepo when the lab grows past the demo phase.

Stages

The OCP-targeting Jenkins pipeline has eight user-visible stages plus a post {} cleanup block. Each stage has a deterministic failure signature.

1. Prepare Workspace

Verifies podman, skopeo, trivy, mc, and the ops-workspace script set are present on the agent. Sets XDG_RUNTIME_DIR to a workspace-local directory so rootless Podman doesn’t fight with other concurrent users on the agent.

SymptomCauseFix
podman system info failsXDG_RUNTIME_DIR missing or wrong permsThe stage runs install -d -m 0700 $WORKSPACE/.xdg-runtime; persistent failure points at agent-side Podman corruption (rebuild or reinstall).
ops-workspace/scripts/update-overlay-digest.sh missing/opt/ops-workspace checkout absent or staleSSH to the agent, git clone https://github.com/zeshaq/ops-workspace.git /opt/ops-workspace (or git -C /opt/ops-workspace pull).

2. Checkout App Source

checkout scm against the tenant repo. The job’s SCM credential is the read-only deploy token for that GitLab project (not the overlay-bot PAT — that’s used only for the digest-patch push).

SymptomCauseFix
Authentication failed on git fetchJob’s SCM credential wrongJenkins job → Pipeline → Pipeline script from SCM → Credentials. Use the per-project read-only deploy token.
Webhook fires but no build startsPolled trigger not registered yetTrigger one manual build through the Jenkins UI to register the polled trigger.

3. Registry Login

podman login against both docker-group.* and app-registry.* using nexus-jenkinsbot.

SymptomCauseFix
denied: access forbiddenNexus role missing on nexus-jenkinsbot for the relevant repoOperator: Nexus UI → Security → Users → jenkinsbot → roles. Required roles: nx-developer-app-push and nx-developer-base-pull.
TLS verification errorNexus TLS cert rotated, agent has stale CAupdate-ca-trust on agent; rerun.

4. Build Image

Loads lib/build-<LANGUAGE>.groovy from the shared library and runs the language-specific build. This is the only stage that varies between Liberty / Node / Spring Boot.

SymptomCauseFix
no Containerfile/Dockerfile in repo rootTenant repo missing build recipeTenant fix: add Containerfile at repo root that FROMs a base hosted on docker-group.*.
Maven artifact resolution failsMaven settings.xml points outside lab Nexus groupUse lab settings.xml template from examples/app-repo-template/.
npm registry timeoutnpm not pointed at lab proxyTenant fix: npm config set registry https://npm-group.apps.sub.comptech-lab.com/ or commit .npmrc.
Language lib not foundTenant repo didn’t vendor shared library and /opt/ops-workspace doesn’t have sibling opp-full-plat/ checkoutOperator: clone opp-full-plat next to ops-workspace on the agent.

5. Trivy Scan Gate

Saves the local image as a tar, runs Trivy in server mode against the Trivy VM, fails the pipeline on any CRITICAL CVE. Produces trivy-scan.json and trivy-scan.txt.

Severity policy (identical for both paths, codified in ci-evidence-schema.md):

SeverityAction
CRITICALFail build. No overlay patch MR is opened. Evidence still uploaded for triage.
HIGHWarn. Build succeeds; trivy-scan.json captures the finding; DefectDojo (when present) flags for triage.
MEDIUM / LOW / UNKNOWNCaptured in JSON; no gate.
SymptomCauseFix
Exit code 1, CRITICAL list in trivy-scan.txtReal CVE in dependencies or base imageTenant fix: bump dependency / base image, re-push. Pipeline stays strict.
connection refused to Trivy serverTrivy VM down or HAProxy backend staleOperator: curl -fsS https://trivy.apps.sub.comptech-lab.com/healthz. If 502, check HAProxy trivy-vm-be backend; if backend down, SSH the Trivy VM.
Trivy hangs > 10 minDB pull stalled on cold startOperator: pre-warm DB on Trivy VM with trivy image --download-db-only.

A CRITICAL failure is not a pipeline bug. The post { unsuccessful {} } block deletes the rejected image from Nexus so a vulnerable manifest is never addressable.

6. Push to Nexus app-registry

Pushes the locally-built image and captures the digest from both the local push and a skopeo inspect against the registry. The two must match; mismatch indicates registry rewriting.

SymptomCauseFix
unauthorized: authentication required mid-pushToken expired between login and push (rare)disableConcurrentBuilds() is already on; verify no parallel job is running.
push digest != registry digestNexus rewriting manifestsOperator: check Nexus blob store storage; check that docker-dev-hosted is not behind an upstream proxy by accident.
manifest blob unknownLayer push interruptedRetry build; intermittent network.

7. Publish Evidence to MinIO

Uploads build.log, sbom.spdx.json, trivy-scan.json/.txt, image-digest.txt, image-registry-digest.txt under developer-ci-evidence/<team>/<app>/<git-sha>/. Also archives the same artifacts on the Jenkins build.

The schema is codified in ci-evidence-schema.md (DEV-OCP-3.7, #195) and is identical between Path A and Path B.

SymptomCauseFix
mc cp returns 403minio-developer-ci-evidence user lost write policyOperator: MinIO console, attach developer-ci-evidence-writer policy to user.
mc cp connection timeoutMinIO VM unreachable from agentOperator: from agent, nc -zv <minio-ip> 9000; if blocked, check br30 routing.

8. Patch GitOps Overlay

Clones the app monorepo, invokes:

ops-workspace/scripts/update-overlay-digest.sh \
  team-<TEAM> <APP> <env> <digest>

and pushes the commit bump: team-<TEAM>/<APP> <env> @<sha256-short> to APP_MONOREPO_BRANCH (typically ci/dev/<short-sha>). The push uses gitlab-mavenbot-pat embedded into the clone URL on the fly; the token is never logged because the sed rewrite happens inside the shell step before the git call.

SymptomCauseFix
ERROR: no 'digest: sha256:<64-hex>' line foundOverlay file doesn’t have a seed digest: lineTenant fix: edit overlays/<env>/kustomization.yaml, add images: block with placeholder digest (see app-repo-contract). Re-run pipeline.
ERROR: overlay kustomization not foundapps/<team>/<app>/overlays/<env>/ doesn’t existTenant fix: open overlay scaffold MR from examples/app-repo-template/.
git push rejectedBranch protected and overlay-bot not on allow listOperator: GitLab → Settings → Repository → Push rules; add overlay-bot identity or grant Maintainer on project.
Permission denied (publickey)PAT clone URL fell back to SSHVerify APP_MONOREPO_URL is https://..., not git@....

9. Promote (optional)

Runs only when both PROMOTE_FROM and PROMOTE_TO are set on the build. Reuses the clone from stage 8, invokes:

ops-workspace/scripts/promote.sh \
  team-<TEAM> <APP> <PROMOTE_FROM> <PROMOTE_TO>

and pushes promote: team-<TEAM>/<APP> <from> @<sha> -> <to>.

SymptomCauseFix
no-op: <to> already pinned at @sha256:...Idempotent run, promotion already doneNothing to do; script is by design idempotent.
ERROR: no 'digest: sha256:...' entry found under images: in apps/.../<to-env>/kustomization.yamlTarget overlay missing images: blockTenant fix: seed target overlay first (one-off manual MR).

Argo sync after the push

Once the digest patch lands on main, the platform ApplicationSet (DEV-OCP-2.2 / #183) sees the new commit on its next refresh (default 3 minutes; can be forced with argocd app get team-<TEAM>-<APP>-dev --refresh). The Argo app then syncs the Deployment, which causes the kubelet to pull the new image by digest from Nexus.

SymptomCauseFix
Argo shows OutOfSync indefinitelySpoke Argo can’t pull from Nexus (cluster pull secret missing)Operator: check connection-details/app-registry-pullsecret.md; ensure apps-pull-secret present in target namespace.
Argo shows Synced / Healthy but no rolloutImage digest unchanged in rendered manifestoc -n <ns> get deploy <app> -o yaml | grep image:. If digest matches but pods are old, oc rollout restart deploy/<app>.
Argo silent / not syncing at allgitops-addon CRD collision (incident #153)Operator: oc delete crd routes.route.openshift.io on the spoke.

Live validation

Live validation on 2026-05-09 of the existing openliberty-readiness-probe-image-build job (the docker-runtime variant that maps to the same chain):

  • Build #8 completed successfully
  • Ran on jenkins-agent-0
  • Cloned divisions/sandbox/openliberty-readiness-probe
  • Pushed app-registry.apps.sub.comptech-lab.com/smoke/readiness-probe:build-8
  • Enforced Trivy gate, archived reports, uploaded release evidence to MinIO
  • Nexus returned HTTP 200 for the pushed manifest

The OCP variant of this job (with stage 8 and 9 enabled) shares all stages 1-7 with the docker-runtime variant. Migration is a job-template parameter change.

When to choose Path A

Choose Path A when:

  • The tenant already maintains a Jenkinsfile and a developer-build job that targets docker-runtime-vm; extending it to also write the GitOps patch is the lowest-effort change.
  • The build needs tools that are easier to install once on jenkins-agent-0 than to package into an OCP container (licensed compilers, large local caches).
  • There is no requirement to run the build inside the target OpenShift cluster (no Tekton-based supply-chain attestation gate yet).
  • The build benefits from warm mvn / podman layer caches.
  • The team operates under an air-gapped change-window and wants the build to survive an OCP outage.

The full decision matrix with example apps is in 06-path-decision-matrix.mdx.

Health-check commands

Cold-start sanity, run from any host that can resolve lab DNS and has ~/.netrc-jenkins configured:

# Jenkins agent online?
curl --netrc-file ~/.netrc-jenkins -fsS \
  https://jenkins.apps.sub.comptech-lab.com/computer/jenkins-agent-0/api/json \
  | jq '{offline: .offline, idle: .idle}'

# Latest OCP-targeted build status (replace job name)?
curl --netrc-file ~/.netrc-jenkins -fsS \
  https://jenkins.apps.sub.comptech-lab.com/job/team-platform-sample-ocp-build/lastBuild/api/json \
  | jq '{number, result, duration, building}'

# Most recent overlay patch landed in the monorepo?
git -C /tmp/app-monorepo-check fetch --quiet origin main
git -C /tmp/app-monorepo-check log --oneline -5 origin/main \
  -- apps/team-platform/sample/overlays/dev/kustomization.yaml

References

  • connection-details/jenkins-ocp-path.md — operator-level reference for Path A (this page is the public-doc version)
  • connection-details/jenkins.md — Jenkins service endpoint, credentials, smoke jobs
  • connection-details/app-repo-contract.md — overlay shape this path writes to (#182)
  • connection-details/image-digest-overlay.md — digest patch convention (#185)
  • connection-details/promotion-model.md — build-once / promote-by-digest model (#184)
  • connection-details/ci-evidence-schema.md — evidence schema (#195)
  • connection-details/nexus.md — Nexus endpoint, roles
  • templates/jenkinsfiles/README.md — Jenkinsfile templates and smoke tests
  • adr/0009-jenkins-single-vm.md — why Jenkins is a single VM
  • adr/0014-developer-readiness-platform-contract.md
  • adr/0018-acm-openshift-gitops-pull-model-v6.md
  • adr/0019-nexus-only-image-supply-chain.md
  • DEV-OCP issues: #187 (extend Jenkins jobs to OCP), #188 (overlay digest-patch step)

Last reviewed: 2026-05-11