calricon.

General

The Container Image Tag Mutation Trap: Why Mutable Tags and 'IfNotPresent' Silently Break Kubernetes Production

Oct 24, 2026/11 min read

calricon / field note

Most DevOps teams know to avoid the ':latest' tag, yet still overwrite semantic tags like 'v1.4' or 'prod' during hotfixes. Because Kubernetes defaults to 'imagePullPolicy: IfNotPresent' for non-latest tags, older worker nodes boot stale cached binaries while autoscaled nodes pull the new build—splitting your cluster in two. Here is how to enforce cryptographic image immutability.

Oct 24, 2026/General
235
Advertisement

The Container Image Tag Mutation Trap: Why Mutable Tags and 'IfNotPresent' Silently Break Kubernetes Production

Every junior DevOps engineer learns on day one never to deploy the :latest container tag to production. Yet across thousands of enterprise Kubernetes clusters, engineering teams routinely commit the exact same architectural sin under a different disguise: they use floating environment tags like :prod, :stable, or :main, or they configure CI/CD pipelines to re-tag and overwrite semantic version strings like :v1.4.2 when pushing an emergency hotfix. The assumption is that container tags represent versioned software artifacts. In reality, an OCI registry tag is nothing more than a mutable, unverified text pointer that can be reassigned to a completely different binary payload at any millisecond. When teams combine mutable tags with Kubernetes' default imagePullPolicy: IfNotPresent, they walk straight into the Container Image Tag Mutation Trap: older worker nodes continue booting stale cached images from their local containerd disk store, while newly autoscaled nodes pull the overwritten image from the registry. Within minutes, a single Kubernetes Deployment splits into a schizophrenic fleet running two incompatible codebases under the exact same tag string.

Banning the :latest tag does not make your cluster reproducible. If your registry allows a CI pipeline to overwrite v1.4.2 with a hotfix, Kubernetes' default IfNotPresent pull policy guarantees that half your nodes will run yesterday's cached binary while freshly autoscaled nodes run today's code—creating an invisible split-brain outage that kubectl describe will never show you.

The Mechanics of OCI Tag Mutability and Kubelet Image Caching

To understand how two pods belonging to the exact same Kubernetes ReplicaSet can execute completely different binaries while reporting the exact same image field in kubectl get pods, platform engineers must trace how the kubelet interacts with the Container Runtime Interface (CRI) and OCI registries:

  • Tags Are Pointers, Digests Are Artifacts: In an OCI-compliant container registry (AWS ECR, Google Artifact Registry, Docker Hub, Harbor), an image is uniquely identified by its cryptographic SHA-256 manifest digest (e.g., sha256:4a8b9c...). A tag (like v1.4.2) is merely a human-readable alias pointing to a digest. Unless explicitly locked by registry policy, pushing a new build with the tag v1.4.2 simply moves the pointer to the new manifest digest, leaving the old manifest orphaned or cached downstream.
  • The Kubernetes Default Pull Policy Rule: In Kubernetes, if a container specification uses any tag other than :latest and does not explicitly define an imagePullPolicy, the API server automatically defaults the policy to IfNotPresent.
  • Node-Local CRI Cache Resolution: When the kubelet schedules a pod onto a worker node with imagePullPolicy: IfNotPresent, it instructs containerd (or CRI-O) to check its local node filesystem first. Crucially, if the container image reference only specifies a tag (registry.example.com/checkout:v1.4.2) without an @sha256:... digest, containerd only checks whether any image tagged v1.4.2 already exists in the node's local image store. If it finds a match on disk, it never contacts the remote registry to verify whether the tag's upstream SHA-256 digest has changed.

The Three Production Disasters of Mutable Image Tags

When mutable tags meet real-world cluster operations, three distinct failure modes emerge to sabotage reliability and security:

1. The Split-Brain Cluster During Horizontal Autoscaling

Imagine a production incident at 2:00 PM. An engineer discovers a critical null-pointer bug in checkout-service:v2.1.0. To save time without bumping version numbers across Helm charts, they patch the code, rebuild the image as checkout-service:v2.1.0, push it to the registry, and run kubectl rollout restart deployment/checkout-service.

Here is how Kubernetes betrays them:

  • Nodes 1 through 15 were already running pods from the original v2.1.0 build. When the new pods are scheduled onto those existing nodes, the kubelet sees imagePullPolicy: IfNotPresent, finds checkout-service:v2.1.0 already sitting in the local containerd cache, and immediately starts the old, buggy binary.
  • At 2:30 PM, traffic surges and Karpenter provisions Nodes 16 through 20. Because these fresh EC2 instances have empty disk caches, their kubelets reach out to the container registry and pull the newly overwritten v2.1.0 image.
  • The cluster is now in a silent split-brain state: 75% of pods are running the buggy code, 25% are running the patched code, and every observability dashboard claims 100% of the fleet is running checkout-service:v2.1.0.

2. The imagePullPolicy: Always Overcorrection and Registry Outage Amplification

Once burned by split-brain node caches, most teams overcorrect by forcing imagePullPolicy: Always across every Deployment in the cluster. While this forces the kubelet to query the container registry for the latest manifest digest on every pod startup, it introduces a massive availability anti-pattern:

  • You have now coupled your Kubernetes pod scheduling path directly to the uptime and rate limits of your external container registry.
  • During a region-wide node scaling event or pod eviction storm, hundreds of kubelets simultaneously hammer your registry to validate manifest digests.
  • Worse yet, if your container registry suffers a transient outage, DNS failure, or authentication hiccup, Kubernetes refuses to start pods even when the exact image binary is already sitting cached on the worker node's local SSD. Your self-healing cluster is paralyzed by an external dependency during the exact moment you need failover resilience.

3. Silent Supply Chain Poisoning and Audit Evasion

From a security and compliance standpoint, mutable tags destroy forensic auditability. If an attacker compromises a CI/CD service account, they do not need to touch your GitOps repository or trigger a pull request in ArgoCD. They can simply push a backdoored container image over an existing production tag (:v2.1.0). The next time a node scales up in your cluster, Kubernetes dutifully pulls and executes the compromised payload while Git history shows zero configuration changes.

Comparing Kubernetes Image Deployment Strategies

Evaluation Dimension Mutable Tags + IfNotPresent (Default) Mutable Tags + imagePullPolicy: Always Calricon Digest-Pinned Architecture (@sha256 + IfNotPresent)
Node Cache Consistency Broken (Stale binaries persist on old nodes) Consistent at startup (Queries registry every boot) 100% Cryptographically Guaranteed
Registry Outage Resilience High (Boots from local disk cache) Fatal (Pods fail to start if registry is down) Maximum (Safe local cache hits)
Cold-Start Pod Latency Fast (Zero network round-trip on cache hit) Degraded (Mandatory network manifest check) Instantaneous (Verified local disk boot)
Supply Chain Auditability Zero (Tag can be overwritten silently) Low (No cryptographic proof in GitOps) Absolute (Immutable Git-to-Node provenance)

The Calricon Production Playbook: Enforcing End-to-End Artifact Immutability

Eliminating node cache drift and registry coupling requires locking tags at the registry layer, pinning workloads by cryptographic digest, and enforcing admission-time verification with Kyverno and Sigstore Cosign.

Step 1: Lock Tag Mutability at the Container Registry Layer

Your first line of defense is configuring your container registry to reject any attempt to overwrite an existing tag. In AWS Elastic Container Registry (ECR), set image_tag_mutability to IMMUTABLE via Terraform:

resource "aws_ecr_repository" "checkout_service" {
  name                 = "production/checkout-service"
  image_tag_mutability = "IMMUTABLE"

  image_scanning_configuration {
    scan_on_push = true
  }

  encryption_configuration {
    encryption_type = "KMS"
  }
}

Once enabled, if a developer or rogue CI job attempts to push a second image with the tag v2.1.0, ECR immediately aborts the push with an ImageTagAlreadyExistsException. Every code change is forced to generate a unique, traceable tag (such as v2.1.1 or git-sha-8f92a1c).

Step 2: Pin Kubernetes Manifests by Tag AND SHA-256 Digest

Never deploy a tag alone in production Kubernetes manifests. Use the combined tag@sha256:digest syntax alongside imagePullPolicy: IfNotPresent:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: checkout-service
  namespace: production
spec:
  replicas: 12
  template:
    spec:
      containers:
        - name: checkout
          # Human-readable tag paired with immutable cryptographic digest
          image: 123456789012.dkr.ecr.us-east-1.amazonaws.com/production/checkout-service:v2.1.0@sha256:6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b
          imagePullPolicy: IfNotPresent

This combination gives you the best of both worlds:

  • Human Readability: Engineers running kubectl get pods can still read v2.1.0 at a glance.
  • Zero Cache Drift: Because Kubernetes resolves the image by its @sha256:... digest, a hotfix produces a brand-new digest that forces every node in the cluster to pull the exact new binary.
  • Registry Outage Immunity: Because IfNotPresent is safe to use with immutable digests, existing nodes can restart pods directly from local SSD cache even if your container registry goes completely offline.

Step 3: Enforce Automatic Digest Resolution and Signature Verification via Kyverno

To prevent human error from bypassing digest pinning, deploy a Kyverno ClusterPolicy in your Kubernetes admission controller. Using Kyverno's verifyImages rule with mutateDigest: true, the admission webhook verifies the image's cryptographic Sigstore Cosign signature and automatically rewrites any tag-only image reference into its immutable @sha256 digest before the pod is ever persisted to etcd:

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: enforce-immutable-signed-images
spec:
  validationFailureAction: Enforce
  background: false
  webhookTimeoutSeconds: 10
  rules:
    - name: verify-cosign-and-pin-digest
      match:
        any:
          - resources:
              kinds:
                - Pod
              namespaces:
                - production
      verifyImages:
        - imageReferences:
            - "123456789012.dkr.ecr.us-east-1.amazonaws.com/production/*"
          mutateDigest: true
          verifyDigest: true
          required: true
          attestors:
            - count: 1
              entries:
                - keys:
                    publicKeys: |-
                      -----BEGIN PUBLIC KEY-----
                      MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE8nX...
                      -----END PUBLIC KEY-----

Step 4: Audit Running Clusters for Split-Brain Digest Drift

You can detect whether your production clusters are currently suffering from split-brain tag drift right now by querying the imageID field in Pod status across your namespaces. Execute this one-liner in your terminal to uncover deployments where identical image tags resolve to multiple distinct SHA-256 digests across different worker nodes:

kubectl get pods -A -o json | jq -r '
  .items[]
  | select(.status.phase=="Running")
  | .status.containerStatuses[]?
  | "\(.image) -> \(.imageID)"
' | sort -u | awk -F' -> ' '
  { count[$1]++; digests[$1]=digests[$1] "\n   " $2 }
  END {
    for (img in count)
      if (count[img] > 1)
        print "ALERT: Split-Brain Tag Detected for " img ":" digests[img]
  }
'

Stop Trusting Mutable Pointers in Production

Treating a container tag as an immutable version identifier is a relic of early Docker workflows that has no place in enterprise Kubernetes engineering. As long as your registries permit tag overwrites and your manifests omit SHA-256 digests, you are one hotfix away from a split-brain production cluster or an invisible supply chain compromise.

Audit your container pipeline today. Flip every production registry repository to IMMUTABLE, update your CI/CD promotion steps to inject @sha256 digests into your GitOps manifests, return your pull policies to IfNotPresent for maximum resilience, and enforce Cosign verification at the admission webhook. In production infrastructure, if an artifact is not pinned by a cryptographic hash, you do not actually know what code you are running.

Advertisement
Talk to Calricon