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.
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
sha256digest. A tag (nginx:1.27) is a mutable human label that can be repointed at any moment; a digest (nginx@sha256:abc…) is immutable.imagePullPolicydecides when the kubelet re-fetches an image, andimagePullSecretssupplies 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:
- Layers are shared. Ten images built
FROM debian:12-slimstore that 74 MB layer once on the node. Pulling the tenth image downloads only its unique layers. - 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.
- 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 kind | Mutable? | Meaning |
|---|---|---|
Tag api:2.4.1 | Yes | A named pointer. Anyone with push rights can move 2.4.1 to different content tomorrow. |
Digest api@sha256:9f2c… | No | The content-addressable hash of the manifest. Different bytes ⇒ different digest, always. |
Why
latestis dangerous.latestis not a magic "newest" keyword — it is just the default tag string. Two nodes pullingmyapp:latestan 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
| Value | Behaviour |
|---|---|
IfNotPresent | Pull only when the image is absent from the node's local store. Fast; the sensible production default with pinned tags. |
Always | Contact 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. |
Never | Never 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
| Concern | Practice |
|---|---|
| Promotion | Copy the same digest between dev, staging, and prod repositories. Never rebuild per environment — a rebuild is a different artifact. |
| Retention | Garbage-collect untagged manifests on a schedule; layer storage grows relentlessly otherwise. |
| Scanning | Scan on push and re-scan periodically — a clean image becomes vulnerable when a new CVE is disclosed against a package it already contains. |
| Signing | Sign the digest with Cosign/Sigstore and verify at admission, so the cluster refuses unsigned images. |
| Caching | Run a pull-through mirror to survive upstream registry outages and rate limits. |
| Air-gap | Export 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.
A Pod spec references myapp:2.4.1 and omits imagePullPolicy. What policy does Kubernetes apply?
A build adds a private key in one layer and deletes it in a later layer. Is the key retrievable from the published image?
Why do high-assurance deployments reference images by sha256 digest rather than by tag?