Git, CI/CD & GitOpsCXI · Kubernetes Delivery PipelineImageArtifact
Container image build and push — the immutable artefact
What you'll learn
- Build and push a container image in CI with a content-addressed digest
- Wire SBOM and cosign attestations to the build step
- Land the image digest in the rendered manifest so promotion is digest promotion
- Recognise why the image digest, not the tag, is the unit of promotion between environments
Prerequisites
Verified against Git 2.55.x teaching target; 2.40+ minimum · GitHub Actions continuous service; Aug 2026 documentation baseline · Argo CD v3.5.x teaching target; v3.0+ minimum · Flux v2.9.x · Sigstore Cosign v3.1.x · SLSA v1.2 · OCI Distribution Specification v1.1 · Git LFS v3.7.1 · Kubernetes (cross-course target) 1.36.x
The container image is one of the two artefacts the cluster consumes. It must be content-addressed, signed, and promoted by digest, not by tag. CI’s job in this part of the pipeline is to build the image, push it to the registry, attest it, and land the digest in the rendered manifest. The GitOps controller never rebuilds; it consumes the digest CI published.
Build, push, attest
flowchart LR
A["Source commit"] --> B["docker buildx build --push"]
B --> C["Image digest in registry"]
C --> D["syft or trivy SBOM"]
D --> E["cosign attest --predicate SBOM"]
C --> F["cosign sign --keyless"]
C --> G["Render manifest with digest"]
G --> H["OCI manifest bundle"]
The build step is a single invocation that produces a content-addressed image and pushes it to the registry. A typical production invocation:
docker buildx build \
--tag "${REGISTRY}/app:${GIT_SHA}" \
--tag "${REGISTRY}/app:latest" \
--platform linux/amd64,linux/arm64 \
--cache-from "type=registry,ref=${REGISTRY}/app:cache" \
--cache-to "type=registry,ref=${REGISTRY}/app:cache,mode=max" \
--push \
.
The ${GIT_SHA} tag is the per-commit reference. The latest tag is a moving alias and must never be the only reference in a rendered manifest. The push produces an immutable digest in the registry; the tag is just a pointer to that digest.
After the push, two attestations are produced and pushed to the same registry:
cosign sign --yes "${REGISTRY}/app@${DIGEST}"
cosign attest --yes --predicate sbom.json --type spdxjson \
"${REGISTRY}/app@${DIGEST}"
The image, the signature, and the SBOM all key off the same digest. The controller can verify any of them by digest.
Landing the digest in the manifest
The render step in CI must land the digest, not the tag, in the rendered YAML. For a Helm chart, a templated values file supplies the digest:
image:
repository: registry.example.com/app
digest: sha256:8a3f9d2c1e7b6f4a5d9c0e2b1a8f7d6c5b4a3e2d1c0b9a8f7e6d5c4b3a2f1e0d
tag: v3.2.7
For a Kustomize tree, a patch in the overlay sets the digest:
images:
- name: app
digest: sha256:8a3f9d2c1e7b6f4a5d9c0e2b1a8f7d6c5b4a3e2d1c0b9a8f7e6d5c4b3a2f1e0d
Either way, the rendered manifest carries the digest. When the controller syncs, the cluster pulls exactly that image.
Why promotion is digest promotion
The same image digest can be promoted from staging to production without rebuilding. The CI job publishes once to the registry; the values file or overlay is then updated to point to that digest for the next environment. Two manifests, two environments, one immutable blob:
flowchart LR
A["CI build at commit 8a3f9d2"] --> B["registry.example.com/app@sha256:8a3f..."]
B --> C["overlays/staging image: digest 8a3f..."]
B --> D["overlays/prod image: digest 8a3f..."]
C --> E["Argo CD sync to staging"]
D --> F["Argo CD sync to production"]
Rebuilding per environment defeats the purpose. If staging builds commit 8a3f9d2 and production builds the same commit a few minutes later, two different digests can land - even with bit-identical inputs, build timestamps and layers change. Promote the digest, not the build.
What the build step does not do
The build step produces one artefact and its attestations. It does not:
- Decide whether the image is correct. CI validation and human approval do that.
- Apply the image to the cluster. The controller does that.
- Tag for human consumption only. Tags are aliases; the digest is what matters.
- Re-render the manifest with a different digest. If the digest in the manifest must change, a new commit updates the values file or overlay.
Production discipline
- Build once, push once, attest twice. One build produces the image; cosign signs the image and attests the SBOM. Two registry writes, one source artefact.
- Pin the digest in the rendered manifest. Tags in the manifest are for humans; the cluster pulls by digest.
- Promote the digest, not the build. Updating the values file or overlay to point at the existing digest moves the same artefact to the next environment.
- Verify the signature before sync. The controller - or an admission controller - rejects unsigned or wrongly-signed images.
- No
latestin production manifests. Alatesttag is a contract that resolves to whatever someone pushed most recently.
Cross-course references
- Containers for Production Sysadmins - Parts XI-XIV cover image supply chain, SBOM generation, and cosign signing.
- Kubernetes for Production Sysadmins - Parts XI-XIV cover image policy admission and signature verification.
- This course, Part LIII (ContainerSupplyChain) - lessons
git-cicd-gitops-liii-01throughgit-cicd-gitops-liii-06cover the supply chain that produces this artefact. - This course, Part XLV (ImmutableIdentity) - covers content addressing and digest promotion across environments.
Quiz
Knowledge check · 4 questions
Q1. What is the right way for a rendered Kubernetes manifest to reference a container image?
Q2. Rebuilding the same commit per environment - once for staging, once for production - is not necessarily the safest way to ensure environment parity.
Q3. What does `cosign sign` do in this pipeline?
Q4. Diagnose a pipeline where production rebuilds the same commit, and prescribe the correction.
A team runs the same CI build per environment: staging builds commit `8a3f9d2`, then production rebuilds the same commit a few minutes later. The values file references `app:v3.2.7`. Staging sees digest A; production sees digest B. A misconfiguration surfaces only in production. The team compares digests and discovers the discrepancy. The values file is updated to reference digest A in production, but the build pipeline still rebuilds per environment, so the next change repeats the pattern.
Passing score: 75%. Answers are checked in this browser.