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.
Learning objectives
- Create release A and release B from controlled inputs and record their local and registry identities.
-
Publish a mutable
stablealias 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.
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:
- A and B will have different image IDs and registry digests.
-
Moving
stablewill change the tag’s resolved digest but will not change what digest A identifies. -
A pinned consumer using
repository@DIGEST_Awill still receive A after the move. -
A floating consumer using
:stablewith 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
- 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
stablereturned A before the move and B after it. -
Proof that
repo@DIGEST_Areturned 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.
Knowledge check
What two predictions must hold after stable moves
from A to B?
The floating alias resolves B, while
repo@DIGEST_A continues to identify A.
Why keep the base tag text if the base digest is already pinned?
It communicates human release intent while the digest supplies immutable identity.
Which artifact proves the registry accepted the pushed content identity?
The push output/digest, retained together with the repository reference.
Why inspect retained containers before cleanup?
Their requested reference and local image ID provide a direct consumer-to-artifact correlation.
What should you do if loopback HTTP registry use is blocked by platform policy?
Use the documented no-registry simulation and record the limitation; do not weaken daemon or TLS security.
Official references and version notes
-
docker image ls— digest display, local image IDs, and digest-addressed pull/create/run examples. -
docker image inspect— low-level local image metadata, including platform-aware inspection on capable stores. -
docker run— current--pull=missing|always|neversemantics. -
Compose
pull_policy—always,never,missing,build,daily,weekly, andevery_<duration>. - Docker build best practices — pinning base images by digest, controlled updates, and auditability tradeoffs.
-
docker buildx imagetools inspect— registry-side manifest/index and platform inspection. - Image digests — manifest-list/index versus per-platform image digest distinctions.
- OCI Image Index Specification — immutable index descriptors and platform selection.
- OCI Image Manifest Specification — config and layer descriptors addressed by digest.
- Docker Engine 29 release notes — Engine 29.8.1 baseline used when authoring this chapter.
- Buildx releases and BuildKit releases — current build-tool release history.
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.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0
Send only Ethereum/ERC-20 compatible assets to this
address.