6.2 Container Image Anatomy: Layers, Tags, Digests & Pull Policy

Key Takeaways

  • A container image is an ordered stack of read-only filesystem layers plus a JSON configuration, described by a manifest and addressed by a content digest.
  • Tags are mutable pointers that can be moved to a different image at any time, whereas a sha256 digest immutably identifies exact content.
  • imagePullPolicy accepts IfNotPresent, Always, and Never; it defaults to Always when the tag is latest or absent and to IfNotPresent otherwise.
  • Private registries are accessed using an imagePullSecrets reference to a Secret of type kubernetes.io/dockerconfigjson.
  • A multi-architecture image is published as an index that maps each platform, such as linux/amd64 and linux/arm64, to a separate manifest.
Last updated: August 2026

6.2 Container Image Anatomy: Layers, Tags, Digests & Pull Policy

Quick Answer: A container image is a stack of read-only filesystem layers plus a JSON config describing entrypoint, environment, and user, tied together by a manifest and addressed by a sha256 digest. A tag (nginx:1.27) is a mutable human label that can be repointed at any moment; a digest (nginx@sha256:abc…) is immutable. imagePullPolicy decides when the kubelet re-fetches an image, and imagePullSecrets supplies credentials for private registries.

Section 6.1 covered what a container is and which runtimes execute it. This section covers the artifact itself — the part that moves through your pipeline, sits in a registry, and gets pulled onto a node.


1. Layers and the Union Filesystem

An image is built one instruction at a time, and each filesystem-modifying instruction produces a layer — a tarball of the changes relative to the layer beneath it.

┌──────────────────────────────────────────┐  ← container writable layer (ephemeral)
├──────────────────────────────────────────┤
│ L4  COPY app.jar /opt/app.jar     12 MB  │  ← changes most often
├──────────────────────────────────────────┤
│ L3  RUN pip install -r req.txt    88 MB  │
├──────────────────────────────────────────┤
│ L2  RUN apt-get install -y curl    9 MB  │
├──────────────────────────────────────────┤
│ L1  FROM debian:12-slim           74 MB  │  ← changes rarely
└──────────────────────────────────────────┘

The runtime presents these to the process as one merged filesystem using a union filesystem (usually overlayfs). Three consequences follow directly, and all three are testable:

  1. Layers are shared. Ten images built FROM debian:12-slim store that 74 MB layer once on the node. Pulling the tenth image downloads only its unique layers.
  2. The container's writable layer is ephemeral. Anything written inside a running container that is not on a mounted volume dies with the container. This is the mechanical reason containers are called stateless.
  3. Deleted files are not removed, only hidden. If layer 3 adds a private key and layer 4 deletes it, the key is still present in layer 3 and recoverable from the image. You cannot delete a secret out of an image by adding a later RUN rm — the only fix is not to add it, or to use a multi-stage build.

Ordering for Cache Hits

Because a changed layer invalidates every layer above it, dependency installation goes before source copy. Copying package.json/go.mod and installing dependencies first means a code-only change rebuilds one small layer instead of re-downloading the world.


2. Manifests, Digests, and the Image Reference

A full image reference has four parts:

   registry.example.com / platform / api : 2.4.1
   └── registry host ──┘ └ repository ─┘  └ tag ┘

   registry.example.com/platform/api@sha256:9f2c…e41b
                                     └───── digest ─────┘

When no registry is given, clients default to Docker Hub; when no tag is given they default to latest. Neither default is a good idea in a manifest.

Reference kindMutable?Meaning
Tag api:2.4.1YesA named pointer. Anyone with push rights can move 2.4.1 to different content tomorrow.
Digest api@sha256:9f2c…NoThe content-addressable hash of the manifest. Different bytes ⇒ different digest, always.

Why latest is dangerous. latest is not a magic "newest" keyword — it is just the default tag string. Two nodes pulling myapp:latest an hour apart can legitimately run different code, and you have no record of which. Production manifests should pin an immutable version tag, and high-assurance pipelines pin the digest, which is also what makes image signing meaningful.

Multi-Architecture Images

A single tag can resolve to an image index (a manifest list) mapping platforms to distinct manifests:

myapp:2.4.1  ──►  index
                   ├── linux/amd64  → sha256:aaa…
                   ├── linux/arm64  → sha256:bbb…
                   └── linux/s390x  → sha256:ccc…

The node's container runtime selects the entry matching its own architecture. This is how one tag works across x86 servers, ARM edge devices, and Apple Silicon laptops — and why an image built only for amd64 fails on an ARM node with an exec format error.


3. How Kubernetes Pulls Images

imagePullPolicy

ValueBehaviour
IfNotPresentPull only when the image is absent from the node's local store. Fast; the sensible production default with pinned tags.
AlwaysContact the registry on every Pod start to check the digest behind the tag. Correct when a mutable tag must be re-resolved; costs a registry round trip.
NeverNever pull. The image must already be on the node — used in air-gapped clusters and by kind/minikube workflows that side-load images.

The default is conditional, and this is a classic exam item: if imagePullPolicy is omitted, Kubernetes uses Always when the tag is latest or no tag is given, and IfNotPresent for every other tag. The behaviour tries to protect you from stale latest images.

imagePullSecrets

Public images need no credentials. Private registries need a Secret of type kubernetes.io/dockerconfigjson, referenced from the Pod (or, better, attached to the ServiceAccount so every Pod using it inherits the credential):

kubectl create secret docker-registry regcred \
  --docker-server=registry.example.com \
  --docker-username=ci-bot --docker-password="$TOKEN"
spec:
  imagePullSecrets:
  - name: regcred
  containers:
  - name: api
    image: registry.example.com/platform/api:2.4.1
    imagePullPolicy: IfNotPresent

A missing or wrong imagePullSecrets produces ImagePullBackOff with an unauthorized or manifest unknown event — indistinguishable at a glance from a typo in the image name, which is why kubectl describe pod is always the next step.


4. Registry Operations

ConcernPractice
PromotionCopy the same digest between dev, staging, and prod repositories. Never rebuild per environment — a rebuild is a different artifact.
RetentionGarbage-collect untagged manifests on a schedule; layer storage grows relentlessly otherwise.
ScanningScan on push and re-scan periodically — a clean image becomes vulnerable when a new CVE is disclosed against a package it already contains.
SigningSign the digest with Cosign/Sigstore and verify at admission, so the cluster refuses unsigned images.
CachingRun a pull-through mirror to survive upstream registry outages and rate limits.
Air-gapExport images to a tarball, import into an internal registry, and rewrite references.

The OCI Distribution Specification standardises the registry HTTP API, which is why a Harbor, an ECR, and a Docker Hub all speak the same protocol to the same clients — and why registries can now store non-image OCI artifacts such as Helm charts and SBOMs alongside images.

Test Your Knowledge

A Pod spec references myapp:2.4.1 and omits imagePullPolicy. What policy does Kubernetes apply?

A
B
C
D
Test Your Knowledge

A build adds a private key in one layer and deletes it in a later layer. Is the key retrievable from the published image?

A
B
C
D
Test Your Knowledge

Why do high-assurance deployments reference images by sha256 digest rather than by tag?

A
B
C
D