Checkpoint Lab — Multi-Platform Images, QEMU Emulation, Native Builders, Manifest Lists, and Cross-Architecture Delivery
The checkpoint produces a two-platform OCI index, records index and per-platform manifest identities, proves architecture-specific build outputs, documents whether any foreign-architecture execution was native, emulated, or not observed, and leaves a reusable evidence packet for digest-pinned delivery in Chapter 13.
Learning objectives
- Create a reproducible two-platform OCI image/index and capture builder, BuildKit, frontend, source, platform, index, and manifest evidence.
- Verify that each descriptor advertises the expected OS/architecture and maps to a distinct per-platform manifest digest.
- Prove architecture-specific artifact content without requiring foreign-architecture execution, then add runtime evidence only when safely available.
- Document exactly whether each observed execution was native, emulated, or not performed, instead of inferring execution from build success.
- Produce a compact release evidence dossier that bridges naturally to Chapter 13 tag, digest, pull-policy, and reproducibility work.
1. Checkpoint mission
Create one reproducible two-platform OCI release candidate for
linux/amd64 and linux/arm64. The build
must use an isolated builder, preserve exact source and tool
versions, record BUILDPLATFORM/TARGET* evidence, export a top-level
OCI index, identify per-platform manifest digests, inspect
architecture-specific artifacts, and state precisely which variants
were actually executed.
The checkpoint succeeds even if the host cannot run the foreign architecture. In that case, runtime execution for that variant is recorded as not observed. Fabricating a test result is a failure.
2. Write predictions before running commands
Create evidence/00-predictions.txt with at least these
predictions:
- The isolated builder will advertise the platform capability needed to solve both requested outputs, or the preflight will identify the limitation before publication.
-
Both builds will execute the build stage on
BUILDPLATFORM, while their generated artifacts will contain differentTARGETARCHvalues. - The OCI result will expose amd64 and arm64 descriptors with distinct per-platform manifest digests under one top-level release index.
- Creating an OCI archive will not by itself create a local Docker tag.
- Foreign-platform runtime evidence will only be claimed if a native/emulated execution is actually observed.
3. Capture environment and builder assumptions
mkdir -p ch12-checkpoint/{src,evidence,out}
cd ch12-checkpoint
docker version | tee evidence/01-docker-version.txt
docker info | tee evidence/02-docker-info.txt
docker context show | tee evidence/03-context.txt
docker buildx version | tee evidence/04-buildx-version.txt
docker buildx ls | tee evidence/05-builders-before.txt
docker buildx create \
--name devops-academy-ch12-checkpoint \
--driver docker-container \
--use
docker buildx inspect --bootstrap \
| tee evidence/06-builder.txt
From the builder output, record driver, BuildKit version, node endpoint, worker platforms, and any capability limitation. Do not continue to “publication” if the requested platform set cannot be solved; preserve the preflight as the checkpoint result and use the documented simulation path.
4. Create and hash declared inputs
cat > src/release.txt <<'EOF'
chapter=12
release=checkpoint
EOF
sha256sum src/release.txt \
| tee evidence/07-source-sha256.txt
A real release should also record Git commit, dependency locks, base-image digests, compiler identity, and test sources. This synthetic checkpoint isolates platform reasoning.
5. Build definition with explicit platform boundary
# syntax=docker/dockerfile:1
FROM --platform=$BUILDPLATFORM alpine:3.22 AS build
ARG BUILDPLATFORM
ARG BUILDOS
ARG BUILDARCH
ARG TARGETPLATFORM
ARG TARGETOS
ARG TARGETARCH
ARG TARGETVARIANT
WORKDIR /work
COPY src/release.txt ./release.txt
RUN printf 'build_platform=%s\nbuild_os=%s\nbuild_arch=%s\ntarget_platform=%s\ntarget_os=%s\ntarget_arch=%s\ntarget_variant=%s\n' \
"$BUILDPLATFORM" "$BUILDOS" "$BUILDARCH" \
"$TARGETPLATFORM" "$TARGETOS" "$TARGETARCH" "$TARGETVARIANT" \
> platform.txt \
&& cat release.txt >> platform.txt
FROM scratch
COPY --from=build /work/platform.txt /platform.txt
LABEL org.opencontainers.image.title="devops-academy-ch12-checkpoint"
LABEL org.opencontainers.image.version="12"
6. Produce the two-platform OCI artifact
docker buildx build \
--builder devops-academy-ch12-checkpoint \
--platform linux/amd64,linux/arm64 \
--progress=plain \
--metadata-file evidence/08-build-metadata.json \
--output type=oci,dest=out/release.oci.tar \
. 2>&1 | tee evidence/09-build-progress.txt
sha256sum out/release.oci.tar \
| tee evidence/10-archive-sha256.txt
Save the plain progress trace. It is the evidence for where each stage ran and whether BuildKit reused cached vertices. The archive hash is transport-file evidence, not the OCI index digest.
7. Unpack and create an index/manifest evidence dossier
rm -rf out/layout
mkdir -p out/layout
tar -xf out/release.oci.tar -C out/layout
python3 - <<'PY' | tee evidence/11-oci-dossier.txt
import json, pathlib, hashlib
root=pathlib.Path('out/layout')
def read_json(d):
a,h=d.split(':',1)
p=root/'blobs'/a/h
raw=p.read_bytes()
actual=hashlib.sha256(raw).hexdigest()
print('VERIFY',d,'sha256:'+actual,'OK' if h==actual else 'MISMATCH')
return json.loads(raw)
def walk(desc,depth=0):
pad=' '*depth
plat=desc.get('platform') or {}
print(pad+'DESC',desc.get('mediaType'),desc.get('digest'),plat)
obj=read_json(desc['digest'])
if 'manifests' in obj:
for child in obj['manifests']: walk(child,depth+1)
elif 'config' in obj:
print(pad+' CONFIG',obj['config']['digest'])
for layer in obj.get('layers',[]): print(pad+' LAYER',layer['digest'])
idx_raw=(root/'index.json').read_bytes()
print('TOP_INDEX_FILE_SHA256','sha256:'+hashlib.sha256(idx_raw).hexdigest())
idx=json.loads(idx_raw)
for d in idx.get('manifests',[]): walk(d)
PY
The top index.json file in an OCI layout can be a
packaging index whose descriptors point to the exported result
object; follow the descriptor tree rather than assuming one fixed
nesting depth. The dossier verifies blob bytes against their
descriptor digests as it walks.
8. Extract platform.txt from each final image
python3 - <<'PY' | tee evidence/12-platform-files.txt
import io,json,pathlib,tarfile,gzip
root=pathlib.Path('out/layout')
def j(d):
a,h=d.split(':',1); return json.loads((root/'blobs'/a/h).read_text())
def b(d):
a,h=d.split(':',1); return (root/'blobs'/a/h).read_bytes()
def leaf(desc):
obj=j(desc['digest'])
if 'manifests' in obj:
for c in obj['manifests']: yield from leaf(c)
elif 'layers' in obj:
yield desc,obj
idx=json.loads((root/'index.json').read_text())
seen=set()
for top in idx.get('manifests',[]):
for desc,man in leaf(top):
p=desc.get('platform') or {}
key=(p.get('os'),p.get('architecture'),p.get('variant'))
if key in seen or p.get('architecture') not in {'amd64','arm64'}: continue
seen.add(key)
print('\nPLATFORM',key,'MANIFEST',desc['digest'])
for layer in man.get('layers',[]):
raw=b(layer['digest'])
if '+gzip' in layer.get('mediaType',''): raw=gzip.decompress(raw)
try:
with tarfile.open(fileobj=io.BytesIO(raw),mode='r:') as tf:
names={n.lstrip('./'):n for n in tf.getnames()}
if 'platform.txt' in names:
print(tf.extractfile(names['platform.txt']).read().decode().strip())
except tarfile.ReadError:
pass
PY
Expected: both variants report the same build platform if one native worker handled the build stage, while target platform/architecture differs. That proves the intended cross-platform artifact path. It still is not runtime execution evidence.
9. Classify runtime execution evidence
Create evidence/13-execution-provenance.txt with one
row per target:
linux/amd64 | native / emulated / not observed | evidence reference
linux/arm64 | native / emulated / not observed | evidence reference
If the checkpoint artifact remains scratch/data-only, both rows can legitimately be “not observed.” For a real application, run the target binary on native or consciously emulated infrastructure and include command output plus runtime architecture metadata. Never infer native versus emulated execution from the descriptor alone.
10. Verification checklist
- Builder identity and worker platforms captured.
- Source hash captured.
- Build uses explicit two-platform request.
- Plain progress retained.
- OCI archive hash retained.
- Descriptor walk identifies amd64 and arm64 manifests.
- Referenced blob digests verify against content.
- Each platform artifact contains expected TARGET* values.
- No claim of runtime execution exists without explicit evidence.
- No public registry, production credential, or host-wide emulator change was required.
11. Controlled failure drill: remove the build-platform pin in a disposable copy
Copy the Dockerfile to Dockerfile.broken and remove
--platform=$BUILDPLATFORM from the build stage. Attempt
only the arm64 target with plain progress. On a builder without
arm64 execution support this may fail with an exec-format style
error; on an emulation-capable builder it may succeed but run
through emulation. Either outcome is useful evidence. Restore the
correct Dockerfile rather than modifying host capability.
docker buildx build \
--builder devops-academy-ch12-checkpoint \
--platform linux/arm64 \
--progress=plain \
-f Dockerfile.broken \
--output type=oci,dest=out/broken-arm64.oci.tar \
. 2>&1 | tee evidence/14-controlled-failure.txt || true
Interpret the first causal difference: the target stage now requires target-platform execution. Do not install host emulators merely to change the result.
12. Review predictions
Create evidence/15-prediction-results.txt. For every
prediction, record confirmed, rejected, or qualified plus the exact
file/line of supporting evidence. If your builder had different
platform capability than expected, that is a valuable correction to
the mental model.
13. Exact cleanup
docker buildx rm devops-academy-ch12-checkpoint
docker buildx ls | tee evidence/16-builders-after.txt
Remove only the checkpoint directory when you no longer need the OCI archive/evidence. No image-store prune, daemon reset, registry deletion, or host binfmt change is part of this checkpoint.
14. Operational review and Chapter 13 handoff
Chapter 12 turns “multi-arch” into a verifiable chain: requested platform set → builder/worker strategy → build/target variables → platform-specific artifacts → per-platform manifest digests → top-level index → exporter/publication state → per-platform runtime evidence. This prevents a single green build from becoming an unsupported claim that every architecture is production-ready.
Chapter 13 focuses on Image Tags, Digests, Mutable vs Immutable References, Pull Policies, Pinning, and Reproducibility. The index and child manifest identities captured here become the concrete examples for understanding why a mutable tag and an immutable digest are different release references.
Knowledge check
Why does the checkpoint keep an OCI archive even when the daemon can store multi-platform images?
It makes the mandatory evidence portable and independent of daemon image-store configuration while exposing the descriptor graph directly.
What does a different arm64 manifest digest prove?
The arm64 platform descriptor references different manifest content; it does not by itself prove the application passed arm64 runtime tests.
Why verify blobs against descriptor digests?
To show that the content-addressed identity in the index/manifest actually matches the stored bytes.
What is the correct result if the host cannot safely run arm64?
Mark arm64 runtime execution as not observed and keep the build/index/artifact evidence separate.
Why is Chapter 13 the natural next step?
Because the checkpoint now has concrete top-level and per-platform digests that can be contrasted with mutable tags and pull policies.
Official references and version notes
- Docker multi-platform builds — current prerequisites, QEMU, multiple native nodes, cross-compilation, automatic platform arguments, and local image-store guidance.
- containerd image store with Docker Engine — Engine 29 fresh-install default, multi-platform local storage, snapshotters, upgrade caveats, and userns-remap limitation.
- Docker Desktop containerd image store — Desktop image-store behavior and multi-platform/attestation support.
-
docker buildx build—--platform, exporters, metadata, progress, builder selection, and output semantics. -
docker buildx inspect— builder driver, BuildKit nodes, status, and platform capability evidence. -
Automatic platform ARGs
—
BUILDPLATFORM,TARGETPLATFORM,TARGETOS,TARGETARCH, and stage-scope behavior. - OCI Image Index Specification — higher-level descriptors and platform selection.
- OCI Image Manifest Specification — per-platform config and layer descriptors.
- OCI Descriptor Specification — digest, size, media type, and platform metadata.
- Docker Engine 29 release notes — current Engine baseline and multi-platform image load/save updates.
- Buildx releases — current Buildx release history.
- BuildKit releases — current BuildKit and Dockerfile frontend history.
Version-sensitive statements were rechecked against primary
documentation on 2026-09-21. The course 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 image-store/builder behavior actually present. Fresh
Engine 29 installations and Docker Desktop use the containerd
image store by default; upgraded Engine hosts may retain the
classic store. Mandatory exercises therefore use an isolated
docker-container builder and OCI export so learning
does not depend on a registry or on local multi-platform load
support.
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.