Chapter 12Lesson 05~150 minutes

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.

Checkpoint labTwo-platform indexPer-platform digestsExecution provenanceChapter 13 bridge

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.
Chapter 12 evidence baseline — verified 2026-09-21. Mandatory exercises use only synthetic source, chapter-owned isolated Buildx builders, public base images, and local OCI output. At verification time Docker Engine 29.8.1, Buildx 0.37.1, BuildKit 0.33.0, and Dockerfile frontend 1.27.0 are the current course baselines, but every lab records actual versions, builder driver, worker platforms, and image-store behavior. Fresh Engine 29 installations and Docker Desktop use the containerd image store by default and support multi-platform images locally; upgraded Engine hosts can retain the classic store. The mandatory path uses cross-compilation-style BUILDPLATFORM/TARGET* separation and OCI export, so no public registry, cloud account, production daemon, host-wide emulator registration, or real credential is required.

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:

  1. The isolated builder will advertise the platform capability needed to solve both requested outputs, or the preflight will identify the limitation before publication.
  2. Both builds will execute the build stage on BUILDPLATFORM, while their generated artifacts will contain different TARGETARCH values.
  3. The OCI result will expose amd64 and arm64 descriptors with distinct per-platform manifest digests under one top-level release index.
  4. Creating an OCI archive will not by itself create a local Docker tag.
  5. 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.

Next chapter

Next: Image Tags, Digests, Mutable vs Immutable References, Pull Policies, Pinning, and Reproducibility

Carry the index and per-platform digest dossier forward and make release references reproducible.

Knowledge check

Why does the checkpoint keep an OCI archive even when the daemon can store multi-platform images?

What does a different arm64 manifest digest prove?

Why verify blobs against descriptor digests?

What is the correct result if the host cannot safely run arm64?

Why is Chapter 13 the natural next step?

Official references and version notes

Version and compatibility note

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.

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