Kubernetes Intermediate By Samson Tanimawo, PhD Published Aug 28, 2026 8 min read

Kubernetes Image Promotion Pipelines

Same image, four environments, one signed digest. Provenance-by-default, the immutable-digest rule, and the promotion pattern that drops “works in staging” bugs to zero.

Why “rebuild for prod” loses

The most expensive bug in a build pipeline is the one that says “it worked in staging.” The cause: the staging image and the production image are different artifacts. The build ran twice; the inputs drifted; one of them has a different glibc version, a different timezone db, a different anything. The bug only shows up in the binary that wasn’t tested.

The pattern that fixes it is also the simplest: build once, deploy the same artifact to every environment. The artifact is identified by its content-addressed digest (sha256:abc...), not by a tag. The whole pipeline is a sequence of promotions of the same digest through environments.

The benefit isn’t just bug reduction. Provenance, what code is running where, becomes trivially answerable. Rollback becomes deterministic; you point at the previous digest. Compliance audits move from spreadsheets to kubectl get deployment -o yaml | grep image:.

The immutable-digest rule

The core rule: production deployments reference images by digest, not by tag.

The why. Tags are mutable. app:v1.2.3 can be repushed; the digest can’t. The digest is a content hash; if the bytes change, the digest changes. Pinning to a digest means “exactly this artifact, no substitutions.”

The shape. Instead of image: myorg/app:v1.2.3, you write image: myorg/app@sha256:abc123.... Every CI build emits the digest; the deployment manifest references it. The CI pipeline updates the manifest; the GitOps controller applies it; the cluster pulls the exact bytes.

The objection. “Digests are ugly; tags are readable.” Use both: tag the image for human readability, digest for the deployment. The tag is informational; the digest is binding. The deployment YAML has the digest; the changelog has the tag-and-digest pair.

The objection 2. “Floating tags like :latest or :stable are convenient.” They’re convenient until production runs an image you didn’t mean to deploy. Floating tags belong in dev only; never in any pipeline that ends in production.

The promotion pipeline

The promotion pipeline is a sequence of stages, each of which gets the same digest applied to a different environment.

Stage 1: build and digest. CI builds the image once; pushes to the registry; captures the digest. The digest is the artifact ID; everything downstream references it.

Stage 2: deploy to dev. The dev environment gets the digest. Smoke tests run; integration tests run; if they pass, mark the digest “dev-passed.”

Stage 3: promote to staging. Same digest, staging environment. Full test suite; performance tests; canary smoke. If they pass, mark “staging-passed.”

Stage 4: promote to prod-canary. Same digest, 5% of prod traffic. Watch error rate, latency, business metrics. If clean for 30 minutes, mark “canary-passed.”

Stage 5: promote to prod-full. Same digest, 100% of prod. Done. The artifact has now passed four environment-specific verifications; the bytes haven’t changed once.

The implementation. GitOps repo with a directory per environment; each directory has a kustomization.yaml with the image digest. Promotion is a PR that copies the digest from one directory to the next. Argo CD or Flux applies; the cluster updates.

Signing and provenance

The digest gives you immutability; signing gives you authenticity. Cosign (sigstore) signs an image with a key; the cluster admission controller verifies the signature before pulling.

The shape. CI signs the digest after build (cosign sign --key cosign.key registry/app@sha256:...). The signature is stored in the registry alongside the image. At admission, Kyverno or Sigstore policy-controller verifies the signature; unsigned images are rejected.

The provenance shape. SLSA (Supply-chain Levels for Software Artifacts) provenance attaches a record to the image: which CI run built it, which commit, which builder identity. The record is signed; verifiable without trusting the CI logs. At admission, you can require “built by our CI, from our git org, on a protected branch.”

The why. Supply chain attacks are real (SolarWinds, log4shell adjacent). The chain of trust runs from source code to running pod; signing closes the gap from registry to cluster.

Admission policies

The promotion pipeline is only as strong as the cluster’s willingness to enforce it. Three admission rules.

Rule 1: digest required. Reject any pod whose image references a tag instead of a digest. Kyverno policy:

match:
  resources:
    kinds: [Pod]
validate:
  pattern:
    spec:
      containers:
        - image: "*@sha256:*"

Rule 2: signature required. Reject any pod whose image isn’t signed by a trusted key. Sigstore policy-controller does this with a few lines of YAML.

Rule 3: registry restriction. Reject any pod whose image isn’t from your trusted registry. Catches the typo, the dependency-confusion attack, the “I copied a manifest from the internet.”

The combination. Three policies; all three pass; the deployment is authentic, immutable, and traceable. Anything that gets through has been built by your pipeline, signed by your key, and pulled from your registry.

Antipatterns

Re-tagging in promotion. Build app:dev, tag-promote to app:staging, then to app:prod. The bytes are the same but the auditing is bad, you can’t tell at a glance whether prod is the same as staging. Use digests; tags are informational.

Building per environment. One CI pipeline per env, each running its own build. Inputs drift; bugs hide. Build once; promote always.

Floating tags in prod. :latest, :stable, :main in production deployments. The tag drifts the moment someone pushes; production silently runs new code. Always pin to digest in prod.

Skipping signature verification. Signing without verifying is theatre. The verification is what enforces the chain; without it, signing is decoration. Run the admission controller.

What to do this week

Three moves. (1) Audit your prod manifests for tag-based image references. Convert the top-10 deployments to digest references; the rest follow. (2) Set up Kyverno (or whatever admission controller you run) to require @sha256: in image references in prod namespaces. The lint catches future drift. (3) Add cosign signing to the CI build, it’s 5 lines. Verification can come later; the signing has to happen first to have anything to verify against.