6.2 Promote to prod repo
On this page you prove that production desired state is a commit on a second Git remote, carrying the same signed digest you already admitted on stage.
Track 6 is the production container. This is the second page. The prod remote is student_prod_gitops_repo_url, repository lw-student/gitops-prod-spring-boot-lw-poc. The seed digest is sha256:REPLACE_ME_PROD_DIGEST, replicas is 0, and the prod tree has no TrustPolicy file. Admission on lw-poc-prod already landed from 6.1 GitOps + admission. You commit the signed digest. Application lw-poc-prod still tracking the stage remote fails the Check.
Why this control exists
Promotion is a deliberate commit to production desired state. Retargeting the prod Application at the stage remote would make production follow stage, which is a different control, and the Check rejects it. After this page, lw-poc-prod sources the prod remote, the ImagePolicy from 6.1 still holds, and the running digest is the one you signed.
oc tag still does not copy .sig tags. You copy the signature onto the prod ImageStream and sign that destination with the 5.3 key, the same sequence as stage. Konflux Release Service maps to this commit. The term map is Appendix: Konflux mapping.
Lightwell Network and Lightwell Clearinghouse Premier do not change the promote remote. The GAV is already inside the digest.
The figure is the same two rows as 6.1. This page is the bottom row. You replace sha256:REPLACE_ME_PROD_DIGEST with the signed digest and set replicas: 1 on lw-student/gitops-prod-spring-boot-lw-poc. Application lw-poc-prod keeps sourcing that remote.
In a customer engagement, promotion is a merge to a prod GitOps repository, or a Release. You push the digest you already signed. Unsigned deny from 6.1 still holds.
Learning objectives
By the end of this module, you will be able to:
-
Tag the signed image into
lw-poc-prodand copy its signature onto that repository -
Commit the digest and
replicas: 1on the prod GitOps remote -
Show Application
lw-poc-prodsourcinggitops-prod-spring-boot-lw-poc, Synced and Healthy -
Leave the stage Application and the TrustPolicy file on the stage remote
Your change
student_prod_namespace is lw-poc-prod. student_prod_gitops_repo_url is the remote you will clone. Success of the tag is a digest line from the prod ImageStream tag. That digest is the one you signed. cosign copy and cosign sign --key attach the signature ImagePolicy already trusts.
BUILD_NS="$(oc -n gitea get configmap demo-userinfo-gitea -o jsonpath='{.data.student_build_namespace}')"
PROD_NS="$(oc -n gitea get configmap demo-userinfo-gitea -o jsonpath='{.data.student_prod_namespace}')"
PROD_GITOPS="$(oc -n gitea get configmap demo-userinfo-gitea -o jsonpath='{.data.student_prod_gitops_repo_url}')"
echo "PROD_NS=${PROD_NS}"
echo "PROD_GITOPS=${PROD_GITOPS}"
oc tag "${BUILD_NS}/spring-boot-lw-poc:latest" "${PROD_NS}/spring-boot-lw-poc:latest"
oc -n "${PROD_NS}" get istag spring-boot-lw-poc:latest \
-o jsonpath='{.image.metadata.name}{"\n"}'
DIGEST="$(oc -n "${PROD_NS}" get istag spring-boot-lw-poc:latest \
-o jsonpath='{.image.metadata.name}')"
SRC="image-registry.openshift-image-registry.svc:5000/${BUILD_NS}/spring-boot-lw-poc@${DIGEST}"
DST="image-registry.openshift-image-registry.svc:5000/${PROD_NS}/spring-boot-lw-poc@${DIGEST}"
export HOME=/tmp/lab-home
mkdir -p "${HOME}"
cosign login -u unused -p "$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" \
image-registry.openshift-image-registry.svc:5000
cosign copy --allow-http-registry --allow-insecure-registry "${SRC}" "${DST}"
COSIGN_PASSWORD="" cosign sign --key /home/lab-user/lab-trust/cosign.key \
--tlog-upload=false --allow-http-registry --allow-insecure-registry \
"${DST}"
Clone student_prod_gitops_repo_url. The password is the value of ConfigMap key student_password. It goes in the clone URL and stays out of the commit. A fresh clone does not inherit the application repository’s author, so set user.name and user.email the same way as 6.1. Success is a clone at /tmp/gitops-prod whose image.digest is still sha256:REPLACE_ME_PROD_DIGEST and whose replicas is still 0.
PROD_GITOPS="$(oc -n gitea get configmap demo-userinfo-gitea -o jsonpath='{.data.student_prod_gitops_repo_url}')"
: "${STUDENT_USER:=$(oc -n gitea get configmap demo-userinfo-gitea -o jsonpath='{.data.student_username}')}"
: "${STUDENT_PASS:=$(oc -n gitea get configmap demo-userinfo-gitea -o jsonpath='{.data.student_password}')}"
rm -rf /tmp/gitops-prod
hostpath="${PROD_GITOPS#https://}"; hostpath="${hostpath#http://}"
git clone "https://${STUDENT_USER}:${STUDENT_PASS}@${hostpath}" /tmp/gitops-prod
cd /tmp/gitops-prod
git config user.name student
git config user.email student@workshop.local
Replace sha256:REPLACE_ME_PROD_DIGEST with the digest you tagged. Set replicas: 1. Commit and push this remote only. Copying values.yaml or PROMOTE.md from the stage clone onto this remote fails the Check. The block below is that seed. Leaving it in place fails the Check. Pointing Application lw-poc-prod at gitops-spring-boot-lw-poc fails it too.
# Worked example ONLY — still the seed; do not copy this onto the prod remote.
image:
digest: sha256:REPLACE_ME_PROD_DIGEST
replicas: 0
# Also not the Check: point Application lw-poc-prod at gitops-spring-boot-lw-poc (stage).
Check
A push alone may not sync. Hard-refresh Application lw-poc-prod after replicas: 1 is on the prod remote. Success is three facts. .spec.source.repoURL is student_prod_gitops_repo_url, which contains gitops-prod-spring-boot-lw-poc. The Application is Synced and Healthy, and REPLACE_ME_PROD_DIGEST is gone. ImagePolicy from 6.1 is still present, so an unsigned tag is still denied.
The repository name without the -prod- segment, gitops-spring-boot-lw-poc.git, is the stage remote. A prod Application on that URL fails the Check.
PROD_GITOPS="$(oc -n gitea get configmap demo-userinfo-gitea -o jsonpath='{.data.student_prod_gitops_repo_url}')"
# Same as 6.1: push alone may not sync. Hard-refresh after replicas: 1 is on the prod remote.
oc -n openshift-gitops annotate applications.argoproj.io lw-poc-prod \
argocd.argoproj.io/refresh=hard --overwrite
oc -n openshift-gitops get applications.argoproj.io lw-poc-prod \
-o jsonpath='repo={.spec.source.repoURL}{"\n"}sync={.status.sync.status}{"\n"}health={.status.health.status}{"\n"}'
oc -n openshift-gitops get applications.argoproj.io lw-poc-prod \
-o jsonpath='{.spec.source.repoURL}{"\n"}' | grep -F 'gitops-prod-spring-boot-lw-poc' \
&& echo 'prod remote OK' || echo 'STILL TRACKING STAGE OR WRONG REPO'
echo "expected prefix ${PROD_GITOPS}"
oc -n lw-poc-prod get deploy,pods,route,imagepolicy
The capture is Application lw-poc-prod after the refresh: Synced and Healthy, on the signed digest, sourced from the prod remote.
The Job also grades ConfigMap report-16-prod-gitops key how_promote. Set it to prod-remote. Empty or REPLACE_ME fails even if cluster state is correct. Do not copy validate-docs.
oc -n lw-poc-validate edit configmap report-16-prod-gitops
The scored Check is Job validate-16-prod-gitops in lw-poc-validate. Direct nav URLs still work (honor system). Unlimited retries. There is no Solve.
oc -n lw-poc-validate delete job validate-16-prod-gitops --ignore-not-found
TPL='job-16.yaml'
oc -n lw-poc-validate get configmap validate-job-templates \
-o go-template="{{ index .data \"${TPL}\" }}" | oc create -f -
oc -n lw-poc-validate logs -f job/validate-16-prod-gitops
Expect CHECK PASSED: before you continue. CHECK FAILED: names the object to fix. Delete the Job and create it again as often as you need.
The Validate Job grades the prod Application’s source URL and the signed digest. Track 7 still opens if you skip this page. The stage Application and the TrustPolicy file stay where 6.1 left them.
Official documentation
-
Konflux mapping (Release Service maps to this prod commit)

