Git, CI/CD & GitOpsXLV · Artifact ImmutabilityPinning
Immutable tags and digest pinning — why :latest is a contract that resolves to whoever pushes last
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
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
- Pin production deployments by
@sha256:..., never by tag. A deployment that references a tag is a deployment that trusts whoever last pushed. - 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.
- 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
Q1. Which Kubernetes image reference pins the deployment to a specific set of bytes, regardless of any subsequent tag reassignment?
Q2. The :latest tag is special and is the only mutable tag in an OCI registry.
Q3. Name the two complementary disciplines that close the tag-reassignment loop: one at the consumer, one at the registry.
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.