Skip to main content
RunBook Academy

Git, CI/CD & GitOpsXLV · Artifact ImmutabilityPinning

Immutable tags and digest pinning — why :latest is a contract that resolves to whoever pushes last

Advanced⏱ ~25 mingit

What you'll learn

  • Identify a mutable tag as a contract that resolves to whoever pushes last
  • Pin deployments by @sha256:... so the reference survives any tag reassignment
  • Recognise that :v3.2.7 is as reassignable as :latest; only the digest is stable
  • Adopt an immutable-tag policy at the registry level as defence in depth

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

Not yet marked complete on this device.

Tags are mutable pointers; digests are immutable identities. A deployment that pins by :latest or :v3.2.7 is a deployment that trusts whoever last pushed to the registry. A deployment that pins by @sha256:... is a deployment that names the bytes regardless of what the tag index now says. The two are not equivalent: tag pinning is a development convenience; digest pinning is a production contract. The combination of digest pinning and registry-level immutable-tag policy is the production discipline.

What :latest actually means

The :latest tag is a default tag the Docker client applies when no tag is specified. The tag itself is not special; the registry treats it as a normal mutable tag. When a consumer pulls registry.example.com/app:latest, the registry resolves the tag to whatever digest the tag index currently points at. The resolution happens at pull time, in the consumer’s registry client.

flowchart LR
    A[":latest tag"] --> B["Registry tag index"]
    B --> C["Currently points to sha256:abc"]
    C --> D["Consumer pulls :latest"]
    D --> E["Receives sha256:abc — whoever pushed last"]

The phrase ‘last pushed’ hides an important ambiguity. ‘Last’ could mean ‘last by the build pipeline’, ‘last by any account with push credentials’, or ‘last by whoever compromised the push credentials’. The tag does not know which; the tag only knows the most recent write to the tag index.

The :v3.2.7 tag is reassignable too

The same reassignment problem applies to version tags. :v3.2.7 is a string in the tag index; the string is reassignable. A team that deleted a release and re-pushed under the same tag, or a CI pipeline that overwrote the tag with a hotfix build, has changed what :v3.2.7 resolves to without changing the string. A deployment that references :v3.2.7 is silently updated.

Digest pinning in deployment manifests

A deployment manifest references the digest by appending @sha256:... to the image reference:

image: registry.example.com/app@sha256:9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08

The @sha256:... syntax is the digest reference. The container runtime resolves the digest through the registry’s content-addressable store, bypassing the tag index. A re-tag of :v3.2.7 or :latest does not affect the deployment; the deployment is bound to the digest the pipeline recorded.

docker pull registry.example.com/app@sha256:$DIGEST

The pull retrieves the bytes the digest identifies, regardless of what the tag index now says.

Immutable-tag policy as defence in depth

Digest pinning makes the deployment immune to tag reassignment. Registry-level immutable-tag policy makes the registry itself reject tag reassignment. The two together form defence in depth: even if a deployment references a tag by mistake, the registry refuses to overwrite the tag, and the existing bytes are still pinned by the digest they were originally tagged with.

GitHub Actions applies the same principle to third-party actions: pin actions to a full-length commit SHA, not a tag or branch. A tag can be reassigned; a commit SHA cannot. The principle generalises across registries and ecosystems.

Production discipline

  1. Pin production deployments by @sha256:..., never by tag. A deployment that references a tag is a deployment that trusts whoever last pushed.
  2. Enable immutable-tag policy at the registry. Most managed registries support this as a per-repository setting; the registry refuses to overwrite an existing tag.
  3. Record the digest at build time. The pipeline that pushes by tag also resolves and records the digest, then the deployment references the digest, not the tag.

Cross-course references

  • Git, CI/CD & GitOps — Part XLV-02 (Digests) establishes the digest format this lesson applies.
  • Git, CI/CD & GitOps — Part XXXVII (CI Foundations) covers the action-pinning rule that applies the same principle to third-party actions.
  • Linux for Production Sysadmins — Part XII (RepoSecurity) covers apt repository pinning, the OS-level analogue of digest pinning.

Quiz

Knowledge check · 4 questions

  1. Q1. Which Kubernetes image reference pins the deployment to a specific set of bytes, regardless of any subsequent tag reassignment?

  2. Q2. The :latest tag is special and is the only mutable tag in an OCI registry.

  3. Q3. Name the two complementary disciplines that close the tag-reassignment loop: one at the consumer, one at the registry.

  4. Q4. Identify the failure mode and the operational rule that prevents it.

    A team writes 'image: registry.example.com/app:production' into the production deployment manifest. An attacker compromises a CI service account with push credentials to the registry and pushes a malicious image under the :production tag. The next time Kubernetes reconciles the deployment, it pulls the attacker's image because the tag now resolves to the attacker's digest.

Passing score: 75%. Answers are checked in this browser.