Chapter 04Lesson 02~115 minutes

Images, Layers, Content-Addressable Storage, Manifests, Config Objects, and Image Identity: Guided Hands-On Workflow and Core Operations

This hands-on lesson turns image identity into evidence. You will pull a small Docker Official Image, capture its repository digest, inspect local and registry metadata, compare history with layer descriptors, create a disposable alias, and prove that a digest—not a tag—is the stable reference.

Hands-onRepoDigestsimagetoolsHistoryDigest pull

Learning objectives

  • Pull a small versioned image and immediately capture the exact digest returned by the registry.
  • Compare local docker image inspect evidence with registry-side docker buildx imagetools inspect evidence.
  • Explain what docker image history can show and why it is not a complete manifest/provenance record.
  • Create and remove only a disposable local alias while proving the underlying image content remains referenced.
  • Run the image by digest and preserve an evidence packet without depending on mutable tags.
Chapter 04 evidence baseline — verified 2026-09-21. This chapter uses a free/local/disposable Docker path. Version-sensitive image behavior is inspected from the active daemon and registry rather than assumed. The standards baseline is OCI Image Specification v1.1.1; current Docker Engine 29 fresh installs default to the containerd image store, while upgraded hosts can differ. The lab image is a small Docker Official Image, and all immutable references are captured at run time rather than copied from prose.

1. Lab scope and safety boundary

This lab uses the Docker Official Image busybox:1.37.0. The versioned tag is only a starting coordinate. You will capture the digest that your registry returns and use that value for immutable follow-up operations. You will not publish, mutate daemon storage, use a production registry, or run broad prune commands.

Lab-owned objects: one local alias named devops-academy-image-id:ch04 and one container named devops-academy-ch04. The pulled upstream image may already exist on the learner's host, so cleanup deliberately does not delete it.

2. Preflight: prove the active client, daemon, platform, and builder

docker context show
docker version
docker info --format 'OSType={{.OSType}} Arch={{.Architecture}} Driver={{.Driver}}'
docker buildx version
docker buildx ls

Record the active context and server architecture. If docker version cannot reach a server, stop here and repair Chapter 02 connectivity first. If Buildx is unavailable, the local-inspection path still works, but registry-side index inspection must be documented as “not observed” rather than invented.

3. Capture before-state without deleting anything

SOURCE='busybox:1.37.0'
ALIAS='devops-academy-image-id:ch04'
NAME='devops-academy-ch04'

docker image ls --digests busybox
docker image inspect "$SOURCE" --format '{{json .RepoDigests}}' 2>/dev/null || true
docker ps -a --filter "name=^/${NAME}$"

The || true is used only for a read-only “may not exist” observation. It is not used to hide a later lab failure. Preserve the output so you can distinguish “already cached” from “pulled during this lab.”

4. Pull the named image and capture registry identity

docker pull "$SOURCE"

docker image inspect "$SOURCE" \
  --format 'ID={{.Id}}\nRepoDigests={{json .RepoDigests}}\nOS={{.Os}} Arch={{.Architecture}}\nRootFS={{json .RootFS}}'

The pull output should print a digest. RepoDigests provides repository-qualified immutable references recorded for the local image. Do not replace that observed value with a digest copied from this lesson, because multi-platform resolution and published content can change over time.

Capture the first repository digest into a shell variable:

PINNED=$(docker image inspect "$SOURCE" --format '{{index .RepoDigests 0}}')
printf 'Pinned reference: %s\n' "$PINNED"

test -n "$PINNED"

5. Inspect the registry-side index/manifest graph

docker buildx imagetools inspect "$SOURCE"
docker buildx imagetools inspect --raw "$SOURCE" > ch04-registry-object.json

If the top-level object is an OCI index or Docker manifest list, note its digest and the child platform descriptors. If it is a single image manifest, note the config and layer descriptors directly. Media type tells you what kind of document you received; do not infer “multi-platform” from the tag name.

Evidence to record
  • Top-level digest and media type.
  • Your host OS/architecture.
  • The child manifest digest for your platform when an index is present.
  • Config descriptor digest and ordered layer descriptor digests when visible.

6. Compare local ID, RepoDigest, and rootfs DiffIDs

docker image inspect "$SOURCE" --format 'ImageID={{.Id}}'
docker image inspect "$SOURCE" --format 'RepoDigests={{json .RepoDigests}}'
docker image inspect "$SOURCE" --format 'RootFS={{json .RootFS}}'
docker image inspect "$SOURCE" --format 'Created={{.Created}} User={{json .Config.User}} Cmd={{json .Config.Cmd}}'

The local Image ID/config identity, repository digest, and rootfs DiffIDs answer different questions. The local config digest describes the selected image configuration. The repository digest is usable as a registry pull reference. The RootFS.Layers values are DiffIDs for the uncompressed filesystem changesets.

7. Use history as a build narrative, not manifest truth

docker image history --no-trunc "$SOURCE"

History can help explain how an image was assembled and which metadata-only steps exist, but it is not a substitute for manifest/config inspection. A history row can correspond to no filesystem layer, and imported/BuildKit-produced images can show <missing> image IDs in history. For exact content identity, return to the manifest, config, and descriptor digests.

8. Create a disposable alias and prove reference sharing

docker tag "$PINNED" "$ALIAS"

docker image inspect "$SOURCE" --format 'source={{.Id}}'
docker image inspect "$ALIAS"  --format 'alias={{.Id}}'
docker image ls --no-trunc "$ALIAS"

The two references should resolve to the same local image/config ID. Creating the alias did not duplicate every layer. It added another human-readable reference to existing content.

docker image rm "$ALIAS"

docker image inspect "$SOURCE" --format 'still-present={{.Id}}'

That final inspection proves removing the lab alias did not remove the upstream source reference or all shared content.

9. Run by the captured digest

docker rm -f "$NAME" 2>/dev/null || true

docker run --name "$NAME" "$PINNED" sh -c \
  'printf "hello from digest-pinned busybox\n"; uname -m'

docker inspect "$NAME" --format 'Container={{.Id}} Image={{.Image}} Ref={{.Config.Image}} Exit={{.State.ExitCode}}'

.Config.Image preserves the reference used to create the container; .Image is the local image ID/config identity. The successful command proves the selected content can execute on the current platform. It does not prove vulnerability status, provenance, signature trust, or production readiness.

10. Challenge: classify each identifier

Without copying commands from above, collect four values: the tag, the top-level registry digest, the local Image ID, and one rootfs DiffID. For each, write one sentence that states what object it identifies and whether it can be used directly in a registry pull reference. If two values happen to look related, explain why byte-level content identity—not visual similarity—determines equivalence.

11. Guarded cleanup

docker inspect "$NAME" --format '{{.Name}} {{.Config.Image}}' 2>/dev/null || true

docker rm "$NAME" 2>/dev/null || true

docker image inspect "$ALIAS" >/dev/null 2>&1 && docker image rm "$ALIAS" || true

docker ps -a --filter "name=^/${NAME}$"
docker image ls "$ALIAS"

Do not run docker system prune, delete Docker data-root files, or remove busybox:1.37.0 as part of this checkpoint. The source image can predate the lab and may be shared with unrelated work.

Next lesson

Next: Configuration, Design Choices, and Tradeoffs

Use the identities you just observed to decide when tags, digests, multi-platform indexes, minimal bases, and retained local content are appropriate.

Knowledge check

Why capture RepoDigests immediately after the pull?

Why does deleting the disposable alias not necessarily free layer bytes?

What does a successful run by digest prove?

Why should docker history not be used as the only layer inventory?

Official references and version notes

Current baseline, not a frozen requirement

Verified 2026-09-21: Docker Engine 29.8.1 remains the current Engine 29 patch line used by this course baseline. Fresh Docker Engine 29 installations use the containerd image store by default, while upgraded older daemons can retain the classic storage-driver store; userns-remap is a documented exception. OCI Image Specification v1.1.1 is the current stable release. The mandatory lab uses the Docker Official Image busybox:1.37.0 only as a small human-readable starting reference, then captures and uses the registry-provided digest at run time. Always record the actual daemon, storage backend, platform, Buildx version, media types, and resolved digests observed on the learner's system.

Keep the academy open

Support free, practical DevOps education.

Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0 Send only Ethereum/ERC-20 compatible assets to this address.