Chapter 13Lesson 05~135 minutes

Checkpoint Lab — Image Tags, Digests, Mutable vs Immutable References, Pull Policies, Pinning, and Reproducibility

Exercise the entire identity model: publish release A, capture its immutable digest, move a stable alias to release B, prove a pinned consumer still resolves A, prove a floating consumer resolves B, and preserve an evidence packet explaining every identity transition.

Checkpoint labDigest A/BPromotion evidenceVerificationCleanup

Learning objectives

  • Create release A and release B from controlled inputs and record their local and registry identities.
  • Publish a mutable stable alias first to A and then to B while preserving both immutable digests.
  • Prove that a digest-pinned consumer remains on A while an always-pulled floating consumer resolves B.
  • Capture an evidence packet containing host/context, build/base identity, push digests, container correlation, and assumptions.
  • Clean up only Chapter 13 resources and bridge the evidence model into the registry/authentication chapter.
Checkpoint rule. Predict identity changes before executing them. The lab is complete only when your evidence distinguishes tag movement from artifact creation, registry digest from local image ID, and floating pull behavior from digest-pinned behavior.

1. Scenario and predictions

You operate a release channel called stable. Release A has already passed validation. Release B is later approved and stable is deliberately moved. Before touching Docker, write down these predictions:

  1. A and B will have different image IDs and registry digests.
  2. Moving stable will change the tag’s resolved digest but will not change what digest A identifies.
  3. A pinned consumer using repository@DIGEST_A will still receive A after the move.
  4. A floating consumer using :stable with an explicit fresh pull will receive B.

2. Capture tool and context assumptions

mkdir -p chapter13-checkpoint/evidence
cd chapter13-checkpoint

{
  date -u
  docker version
  docker info
  docker context show
  docker buildx version
  docker buildx inspect --bootstrap
} | tee evidence/preflight.txt

Record whether you are on native Linux Engine or Docker Desktop, the active context/endpoint, image-store behavior, builder driver, BuildKit version, worker platform, and any proxy. Do not claim versions you did not observe.

3. Pin the external base input and save the resolution

BASE_TAG="busybox:1.37.0"
docker buildx imagetools inspect "$BASE_TAG" | tee evidence/base.txt

BASE_DIGEST=$(
  awk '/^Digest:/ {print $2; exit}' evidence/base.txt
)
test -n "$BASE_DIGEST"
BASE_REF="busybox:1.37.0@$BASE_DIGEST"
printf '%s\n' "$BASE_REF" | tee evidence/base-ref.txt

The base tag remains readable, while the digest freezes the exact top-level registry object selected for this checkpoint.

4. Create one Dockerfile for both release candidates

cat > Dockerfile <<'EOF'
# syntax=docker/dockerfile:1
ARG BASE_REF
FROM ${BASE_REF}
ARG RELEASE
LABEL devops-academy.lab="chapter13"
LABEL org.opencontainers.image.version="${RELEASE}"
RUN printf 'release=%s\n' "$RELEASE" > /release.txt
CMD ["sh","-c","cat /release.txt"]
EOF

sha256sum Dockerfile | tee evidence/dockerfile.sha256

5. Build A and B and record local identities

for R in A B; do
  docker build \
    --build-arg BASE_REF="$BASE_REF" \
    --build-arg RELEASE="$R" \
    --label devops-academy.lab=chapter13 \
    -t "devops-academy-ch13-${R,,}:local" . \
    | tee "evidence/build-$R.txt"

  docker image inspect "devops-academy-ch13-${R,,}:local" \
    --format 'ID={{.Id}} RepoTags={{json .RepoTags}} RepoDigests={{json .RepoDigests}} labels={{json .Config.Labels}}' \
    | tee "evidence/image-$R.txt"
done

If your shell does not support ${R,,}, run the A and B commands separately. Portability is more important than clever shell syntax.

6. Start the disposable registry and publish A

REGISTRY_TAG="registry:2"
docker buildx imagetools inspect "$REGISTRY_TAG" | tee evidence/registry-image.txt
REGISTRY_DIGEST=$(
  awk '/^Digest:/ {print $2; exit}' evidence/registry-image.txt
)
test -n "$REGISTRY_DIGEST"
REGISTRY_REF="registry:2@$REGISTRY_DIGEST"
printf '%s\n' "$REGISTRY_REF" | tee evidence/registry-ref.txt

docker run -d \
  --name devops-academy-ch13-registry \
  --label devops-academy.lab=chapter13 \
  -p 127.0.0.1:5000:5000 \
  "$REGISTRY_REF"

curl -fsS http://127.0.0.1:5000/v2/ | tee evidence/registry-ready.txt

REPO="localhost:5000/devops-academy/ch13"
docker tag devops-academy-ch13-a:local "$REPO:release-a"
docker tag devops-academy-ch13-a:local "$REPO:stable"
docker push "$REPO:release-a" | tee evidence/push-release-a.txt
docker push "$REPO:stable" | tee evidence/push-stable-a.txt

DIGEST_A=$(
  awk '/digest:/ {
    for (i=1; i<=NF; i++) if ($i=="digest:") {print $(i+1); exit}
  }' evidence/push-stable-a.txt
)
test -n "$DIGEST_A"
printf '%s\n' "$DIGEST_A" | tee evidence/digest-a.txt

7. Prove A before moving the alias

docker run --rm --pull=always "$REPO:stable" \
  | tee evidence/stable-before.txt

docker run --rm --pull=always "$REPO@$DIGEST_A" \
  | tee evidence/pinned-a-before.txt

grep -qx 'release=A' evidence/stable-before.txt
grep -qx 'release=A' evidence/pinned-a-before.txt

8. Move stable to B and capture digest B

docker tag devops-academy-ch13-b:local "$REPO:release-b"
docker tag devops-academy-ch13-b:local "$REPO:stable"
docker push "$REPO:release-b" | tee evidence/push-release-b.txt
docker push "$REPO:stable" | tee evidence/push-stable-b.txt

DIGEST_B=$(
  awk '/digest:/ {
    for (i=1; i<=NF; i++) if ($i=="digest:") {print $(i+1); exit}
  }' evidence/push-stable-b.txt
)
test -n "$DIGEST_B"
test "$DIGEST_A" != "$DIGEST_B"
printf '%s\n' "$DIGEST_B" | tee evidence/digest-b.txt

9. Prove the split: floating B, pinned A

docker run --rm --pull=always "$REPO:stable" \
  | tee evidence/stable-after.txt

docker run --rm --pull=always "$REPO@$DIGEST_A" \
  | tee evidence/pinned-a-after.txt

grep -qx 'release=B' evidence/stable-after.txt
grep -qx 'release=A' evidence/pinned-a-after.txt

This is the checkpoint’s central proof. One human-friendly reference moved; the immutable digest did not.

10. Retain two containers long enough to inspect correlation

docker create --name devops-academy-ch13-c-a "$REPO@$DIGEST_A"
docker create --name devops-academy-ch13-c-b "$REPO:stable"

docker inspect devops-academy-ch13-c-a devops-academy-ch13-c-b \
  --format 'name={{.Name}} requested={{.Config.Image}} localImageID={{.Image}} created={{.Created}}' \
  | tee evidence/containers.txt

docker image inspect "$REPO@$DIGEST_A" \
  --format 'A ID={{.Id}} RepoDigests={{json .RepoDigests}}' \
  | tee evidence/repo-a.txt

docker image inspect "$REPO:stable" \
  --format 'stable ID={{.Id}} RepoDigests={{json .RepoDigests}}' \
  | tee evidence/repo-stable.txt

11. Evidence packet checklist

Required evidence
  • UTC timestamp, host/platform, client/server/API versions, context, endpoint, and builder/BuildKit identity.
  • Human-readable base tag plus resolved base digest.
  • Dockerfile checksum and build logs for A/B.
  • Local image IDs and labels for both releases.
  • Push logs and DIGEST_A/DIGEST_B.
  • Proof that stable returned A before the move and B after it.
  • Proof that repo@DIGEST_A returned A both before and after the move.
  • Container requested reference versus local image ID correlation.
  • Assumptions/limitations note, including whether loopback registry behavior was available.

12. Guarded cleanup and verification

docker rm devops-academy-ch13-c-a devops-academy-ch13-c-b
docker rm -f devops-academy-ch13-registry

docker image rm \
  "$REPO:stable" "$REPO:release-a" "$REPO:release-b" \
  devops-academy-ch13-a:local devops-academy-ch13-b:local \
  2>/dev/null || true

docker ps -a --filter label=devops-academy.lab=chapter13
docker image ls --filter label=devops-academy.lab=chapter13

Empty filtered output confirms the lab-owned containers/images are gone. Do not remove unrelated cache, volumes, networks, or registry data outside this exact lab scope.

13. Operational review and Chapter 14 handoff

You now have a release identity model that separates human aliases from immutable content. Chapter 14 adds the distribution layer: registries, authentication, credential stores, push/pull mechanics, and private-registry operations. Carry forward the same rule—successful authentication, successful push, tag visibility, digest verification, and consumer pull are separate states with separate evidence.

Next chapter

Next: Registries, Docker Hub, Private Registries, Authentication, Credential Stores, Push/Pull, and Distribution

Move from identity semantics into secure distribution and registry operations.

Knowledge check

What two predictions must hold after stable moves from A to B?

Why keep the base tag text if the base digest is already pinned?

Which artifact proves the registry accepted the pushed content identity?

Why inspect retained containers before cleanup?

What should you do if loopback HTTP registry use is blocked by platform policy?

Official references and version notes

Version and compatibility note

Version-sensitive statements were rechecked against primary documentation on 2026-09-21. The authoring baseline is Docker Engine 29.8.1, Buildx 0.37.1, BuildKit 0.33.0, and Dockerfile frontend 1.27.0, but every executable lab records the versions and context actually present. Registry tags, platform indexes, local image stores, and Compose behavior can evolve independently; prefer observed digests and current primary documentation over memorized output.

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.