Checkpoint Lab — Docker in CI/CD: Reproducible Builds, Buildx, Registry Caching, Ephemeral Runners, and Release Promotion
Execute a digest-bound synthetic CI/CD checkpoint that builds once, tests once, records evidence, promotes the same digest, and proves no second build occurred.
Learning objectives
- Execute a synthetic source-to-registry pipeline that produces one immutable release subject.
- Capture source, builder, cache, test, digest and attestation evidence before promotion.
- Promote the same subject under a release alias without invoking a second build.
- Verify downstream identity independently and document any local-registry simulation limitations.
- Produce a concise evidence packet proving “build once, promote same bytes.”
1. Checkpoint contract
The checkpoint is successful only if you can show one build invocation produced the candidate, tests were run against that candidate, publication produced an immutable digest, the release alias points to the same digest, and no second Dockerfile build occurred during promotion. Use the local registry path if supported; otherwise perform the OCI-layout simulation and mark registry-referrer evidence as simulated.
2. Workspace, versions and run identity
set -eu
LAB=dca36-checkpoint
RUN_ID="local-$(date -u +%Y%m%dT%H%M%SZ)"
mkdir -p "$LAB/src" "$LAB/evidence" "$LAB/cache"
printf '%s
' "$RUN_ID" | tee "$LAB/evidence/run-id.txt"
docker context show | tee "$LAB/evidence/context.txt"
docker version | tee "$LAB/evidence/docker-version.txt"
docker compose version | tee "$LAB/evidence/compose-version.txt" || true
docker buildx version | tee "$LAB/evidence/buildx-version.txt"
3. Create source and record revision
cat > dca36-checkpoint/src/Dockerfile <<'EOF'
# syntax=docker/dockerfile:1
FROM alpine:3.22
ARG SOURCE_SHA
LABEL org.opencontainers.image.revision=$SOURCE_SHA
RUN printf 'source=%s
' "$SOURCE_SHA" > /release.txt
CMD ["cat","/release.txt"]
EOF
SOURCE_SHA=$(git rev-parse HEAD 2>/dev/null || sha256sum dca36-checkpoint/src/Dockerfile | cut -d' ' -f1)
printf '%s
' "$SOURCE_SHA" | tee dca36-checkpoint/evidence/source-sha.txt
sha256sum dca36-checkpoint/src/Dockerfile | tee dca36-checkpoint/evidence/dockerfile.sha256
4. Predict state changes before execution
Prediction 1: a new builder named dca36-release will own checkpoint BuildKit state.
Prediction 2: exactly one Dockerfile build invocation will create the candidate.
Prediction 3: candidate test output will contain the recorded SOURCE_SHA.
Prediction 4: build and release registry aliases will resolve to the same immutable digest.
Prediction 5: cleanup will remove only dca36-release, dca36-registry and lab-owned image references/files.
5. Create the ephemeral builder and capture worker evidence
docker buildx create --name dca36-release --driver docker-container --bootstrap
docker buildx inspect dca36-release | tee dca36-checkpoint/evidence/builder.txt
docker buildx du --builder dca36-release | tee dca36-checkpoint/evidence/cache-before.txt
6. Build exactly once and capture metadata
SOURCE_SHA=$(cat dca36-checkpoint/evidence/source-sha.txt)
printf 'BUILD_INVOCATION=1
' | tee dca36-checkpoint/evidence/build-count.txt
docker buildx build --builder dca36-release --build-arg SOURCE_SHA="$SOURCE_SHA" --cache-from type=local,src=dca36-checkpoint/cache --cache-to type=local,dest=dca36-checkpoint/cache-next,mode=max --metadata-file dca36-checkpoint/evidence/build-metadata.json --load -t dca36-checkpoint:candidate dca36-checkpoint/src | tee dca36-checkpoint/evidence/build.log
rm -rf dca36-checkpoint/cache
mv dca36-checkpoint/cache-next dca36-checkpoint/cache
7. Test the exact candidate and capture image identity
SOURCE_SHA=$(cat dca36-checkpoint/evidence/source-sha.txt)
docker run --rm dca36-checkpoint:candidate | tee dca36-checkpoint/evidence/test-output.txt
grep -F "source=$SOURCE_SHA" dca36-checkpoint/evidence/test-output.txt
docker image inspect dca36-checkpoint:candidate --format 'id={{.Id}} labels={{json .Config.Labels}}' | tee dca36-checkpoint/evidence/candidate-inspect.txt
8. Attestation evidence plan
The loaded single-platform lab candidate is convenient for local
tests. For production registry output, add
--provenance=mode=min --sbom=true --push to the one
build invocation and capture registry-attached evidence against the
published digest. If the local registry path below supports
attestations in your environment, you may repeat the checkpoint as a
registry-output-only build—but that would be a separate candidate
and must receive a new evidence chain. Do not pretend the loaded
candidate inherited registry attestations it never had.
9. Publish the already-built candidate to the disposable registry
docker run -d --name dca36-registry --label academy.lab=dca36 -p 127.0.0.1:5000:5000 registry:2
docker tag dca36-checkpoint:candidate 127.0.0.1:5000/dca36/checkpoint:build
docker push 127.0.0.1:5000/dca36/checkpoint:build | tee dca36-checkpoint/evidence/push-build.log
docker buildx imagetools inspect 127.0.0.1:5000/dca36/checkpoint:build | tee dca36-checkpoint/evidence/build-ref.txt
10. Promote without rebuild
printf 'PROMOTION_BUILD_INVOCATIONS=0
' | tee dca36-checkpoint/evidence/promotion-build-count.txt
docker buildx imagetools create --tag 127.0.0.1:5000/dca36/checkpoint:release-1 127.0.0.1:5000/dca36/checkpoint:build
docker buildx imagetools inspect 127.0.0.1:5000/dca36/checkpoint:release-1 | tee dca36-checkpoint/evidence/release-ref.txt
Compare the digest lines in build-ref.txt and
release-ref.txt. A successful checkpoint has the same
subject digest.
11. Downstream identity check
docker pull 127.0.0.1:5000/dca36/checkpoint:release-1 | tee dca36-checkpoint/evidence/pull-release.log
docker image inspect 127.0.0.1:5000/dca36/checkpoint:release-1 \
--format 'id={{.Id}} repoDigests={{json .RepoDigests}} labels={{json .Config.Labels}}' | tee dca36-checkpoint/evidence/downstream-inspect.txt
12. Cache evidence and no-second-build proof
docker buildx du --builder dca36-release | tee dca36-checkpoint/evidence/cache-after.txt
cat dca36-checkpoint/evidence/build-count.txt dca36-checkpoint/evidence/promotion-build-count.txt
The proof is procedural plus artifact-based: the checkpoint records
one build invocation, promotion uses only
imagetools create, and both aliases resolve to the same
registry digest.
13. Exact cleanup
docker rm -f dca36-registry 2>/dev/null || true
docker image rm dca36-checkpoint:candidate 127.0.0.1:5000/dca36/checkpoint:build 127.0.0.1:5000/dca36/checkpoint:release-1 2>/dev/null || true
docker buildx rm dca36-release
printf 'Retain dca36-checkpoint/evidence for review; remove the directory only after review.
'
14. Required evidence packet
| Evidence family | Required record |
|---|---|
| Source | full source SHA or synthetic checksum; Dockerfile checksum |
| Run | run ID and UTC timestamps |
| Builder | Docker/Compose/Buildx versions; builder/worker evidence |
| Cache | before/after builder cache report; cache scope/path |
| Build | single build log; metadata JSON; build-count statement |
| Test | candidate output/assertion linked to source SHA |
| Artifact | candidate image ID and registry build digest |
| Attestations | production plan or registry-bound SBOM/provenance evidence if supported |
| Promotion | release alias inspection; zero-build promotion statement |
| Downstream | pulled RepoDigest/labels and expected subject match |
| Cleanup | exact builder/registry/image resources removed; no broad prune |
| Limitations | local registry/attestation/provider differences explicitly noted |
15. What Chapter 36 adds to the production operating model
You can now design CI/CD around a stable release subject: exact source revision, isolated builder, explicitly scoped cache, one immutable digest, tests and supply-chain evidence bound to that digest, and promotion by aliasing/copying the same content. This makes rollback and incident analysis artifact-centric rather than branch-centric.
Knowledge check
What is the strongest proof that promotion did not rebuild?
The pipeline records one build invocation, promotion performs only registry reference creation/copy, and the build and release aliases resolve to the same digest.
Why are test output and source SHA both in the evidence packet?
They bind the behavior tested to the exact source-derived candidate rather than to a mutable tag.
If the local registry cannot support the required attestation behavior, what should you do?
Use the documented simulation/alternate evidence path and explicitly record the limitation; do not weaken daemon trust globally just to make the lab pass.
Why is the cache report not release evidence?
Cache is performance state; the immutable image/index digest and its verification records are release identity.
What is the natural rollback unit after this chapter?
A previously verified registry digest with its retained tests/attestations and compatibility evidence.
Official references and version notes
Checkpoint baseline: 2026-09-22. Buildx 0.37.1, BuildKit 0.33.0, docker/build-push-action 7.4.0 and setup-buildx-action 4.3.0 are current upstream references. The runnable checkpoint remains provider-neutral and free/local/disposable.
- Docker Docs — Build with CI — CI patterns around Buildx/BuildKit and provider integrations.
- Docker Docs — Cache storage backends — inline, local, registry, and GitHub Actions cache backends and scope considerations.
- Docker Docs — Registry cache — external registry cache configuration and separation from the release image.
-
Docker Docs —
docker buildx build— metadata files, cache import/export, push, SBOM and provenance controls. - Docker Docs — Build attestations — default provenance and SBOM/provenance persistence rules.
- Docker Docs — GitHub Actions attestations — current Docker-maintained action behavior and provenance/SBOM inputs.
- Docker Docs — GitHub Actions cache management — provider cache integration and limits.
-
Docker Docs —
docker buildx imagetools create— create/retag registry manifests without rebuilding. - Docker Docs — Builders — builder instances, drivers, workers, and lifecycle.
- Docker Buildx releases — version source; v0.37.1 is current at this chapter baseline.
- Moby BuildKit releases — version source; v0.33.0 is current at this chapter baseline.
- docker/build-push-action releases — v7.4.0 current at this baseline.
- docker/setup-buildx-action releases — v4.3.0 current at this baseline.
- Docker Engine 29 release notes — Engine 29.8.1 baseline and bundled/runtime changes.
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.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0Send only Ethereum/ERC-20 compatible assets to this
address.