Release Kubernetes workloads with immutable image digests

The DevOpsPad artifact library, where each version is registered by its full sha256 image digest.
The DevOpsPad artifact library, where each version is registered by its full sha256 image digest.

Deploy by digest (registry/app@sha256:…), not by tag (registry/app:1.4.2). A tag is a pointer that anyone with push access can move, so the same manifest can run different code on different days, or on different nodes of the same Deployment. A digest is the hash of the image manifest itself. If it matches, the content matches. It also makes rollbacks exact: you return to the bytes that ran before, not to whatever the tag points at now.

Step 1: get the digest at build time

The best moment to capture a digest is right after the push, from the tool that pushed:

docker buildx build --platform linux/amd64,linux/arm64 \
  -t registry.lan:5000/svc-user:1.4.2 --push \
  --metadata-file build-meta.json .
jq -r '."containerimage.digest"' build-meta.json
# sha256:3f1c…

For an image that already exists, ask the registry:

docker buildx imagetools inspect registry.lan:5000/svc-user:1.4.2
crane digest registry.lan:5000/svc-user:1.4.2
skopeo inspect docker://registry.lan:5000/svc-user:1.4.2 | jq -r .Digest

Avoid docker images --digests and docker inspect … RepoDigests for this. They describe what your local daemon pulled, which may be a single platform’s manifest rather than the one the cluster will resolve.

Step 2: mind multi-architecture indexes

A multi-arch tag points to an index (manifest list), which in turn points to one manifest per platform. Each has its own digest. Use the index digest in your Deployment: each node then pulls the right platform from it. If you pin a platform-specific manifest digest, an arm64 node handed an amd64-only manifest fails with an exec format error.

docker buildx imagetools inspect prints the index digest at the top and the per-platform manifests under it.

Step 3: deploy by digest

spec:
  template:
    spec:
      containers:
        - name: svc-user
          image: registry.lan:5000/svc-user@sha256:3f1c…e9a0

or imperatively:

kubectl -n team-a set image deployment/svc-user \
  svc-user=registry.lan:5000/svc-user@sha256:3f1c…e9a0
kubectl -n team-a rollout status deployment/svc-user --timeout=5m

You can write name:tag@sha256:… for readability. The runtime uses the digest and ignores the tag. Do not combine latest with a digest; it suggests a moving target where there is none and confuses whoever reads it next.

imagePullPolicy matters less with digests: IfNotPresent is safe, because a cached image with the same digest is the same image.

Step 4: record what you released

Keep, for every release: the full digest, the human version (1.4.2), the source commit, the previous digest, and the outcome. With that record, a rollback is “set the image back to the previous digest”, with no searching through registry tags.

kubectl -n team-a get deployment svc-user \
  -o jsonpath='{.spec.template.spec.containers[?(@.name=="svc-user")].image}'

Run this before changing anything. It is the “before” half of the record.

Step 5: roll back to a digest, not to a revision

kubectl rollout undo goes back to the previous ReplicaSet’s whole Pod template. That includes environment variables and resource changes made since, which may not be what you want. Setting the image back to a recorded digest changes only that container’s image:

kubectl -n team-a set image deployment/svc-user \
  svc-user=registry.lan:5000/svc-user@sha256:<previous-digest>

Step 6: keep the registry from deleting what you depend on

Registry garbage collection removes manifests that no tag references. If you clean up old tags, keep at least the last few releases’ tags (or use your registry’s retention rules) so a rollback digest can still be pulled.

Doing this in DevOpsPad

DevOpsPad’s Infra console only releases by digest. Registering an artifact requires the full repository@sha256:<64 lowercase hex> form and rejects references that combine :latest with a digest. Registration is a local record. It does not build, push or check that the digest exists in the registry; the cluster pulls it at release time. Builds reported through devopspadctl runs record must also finish with a digest, and a successful build becomes an artifact automatically.

A release plan shows the current image and the target image side by side, runs a server-side dry-run and, once confirmed, writes a record with both. Rolling back from a successful release record creates a new plan for that record’s digest and changes only the target container’s image, keeping the rest of the current configuration, as in Step 5. See registering artifacts, releasing an image and rolling back a release. The design behind the plans is in dry-run plans and leases for desktop releases.