Docker in CI/CD: Reproducible Builds, Buildx, Registry Caching, Ephemeral Runners, and Release Promotion: Guided Hands-On Workflow and Core Operations
Run a disposable source-to-image workflow with an ephemeral Buildx builder, bounded cache, tests, digest capture, local registry simulation, and exact digest promotion.
Learning objectives
- Create a disposable per-lab Buildx builder and record its exact worker/version evidence.
- Build a synthetic image from a recorded source revision, run a deterministic image test, and capture the resulting digest metadata.
- Use an explicit local cache directory first, then model a registry-cache path without making cloud services mandatory.
- Publish to a disposable local registry where the platform supports it, promote the same digest under a release alias, and verify identity.
- Clean up only exact lab-owned resources.
1. Lab topology and portability note
The mandatory lab uses only Docker, Buildx, Git, shell tools and synthetic content. A local registry is used when your Docker/BuildKit installation can push to loopback without daemon reconfiguration. If your platform blocks that path, execute the build/test/digest/cache steps and use the supplied OCI-layout promotion simulation; the lesson explains which registry claims remain unverified.
Resource prefix: dca36-. No broad prune is used.
2. Preflight and source identity
set -eu
LAB=dca36-lab
mkdir -p "$LAB/src" "$LAB/evidence" "$LAB/cache"
date -u +%Y-%m-%dT%H:%M:%SZ | tee "$LAB/evidence/time-start.txt"
docker context show | tee "$LAB/evidence/context.txt"
docker version | tee "$LAB/evidence/docker-version.txt"
docker buildx version | tee "$LAB/evidence/buildx-version.txt"
git rev-parse HEAD 2>/dev/null | tee "$LAB/evidence/source-sha.txt" || printf 'synthetic-no-git
' | tee "$LAB/evidence/source-sha.txt"
3. Create the deterministic application
cat > dca36-lab/src/app.sh <<'EOF'
#!/bin/sh
set -eu
printf 'release=%s source=%s
' "${RELEASE:-dev}" "${SOURCE_SHA:-unknown}"
EOF
chmod +x dca36-lab/src/app.sh
cat > dca36-lab/src/Dockerfile <<'EOF'
# syntax=docker/dockerfile:1
FROM alpine:3.22
ARG SOURCE_SHA=unknown
LABEL org.opencontainers.image.revision=$SOURCE_SHA
COPY app.sh /usr/local/bin/app
ENTRYPOINT ["/usr/local/bin/app"]
EOF
sha256sum dca36-lab/src/* | tee dca36-lab/evidence/source-files.sha256
4. Provision one ephemeral builder
docker buildx create --name dca36-ci --driver docker-container --bootstrap
docker buildx inspect dca36-ci | tee dca36-lab/evidence/builder.txt
The docker-container driver gives this lab its own
BuildKit worker/cache namespace. In hosted CI, runner provisioning
should be outside untrusted repository code, as Chapter 35
established.
5. Build with an explicit local cache and metadata file
SOURCE_SHA=$(cat dca36-lab/evidence/source-sha.txt)
docker buildx build --builder dca36-ci --build-arg SOURCE_SHA="$SOURCE_SHA" --cache-from type=local,src=dca36-lab/cache --cache-to type=local,dest=dca36-lab/cache-next,mode=max --metadata-file dca36-lab/evidence/build-metadata.json --load -t dca36-app:ci dca36-lab/src
rm -rf dca36-lab/cache
mv dca36-lab/cache-next dca36-lab/cache
The local cache is a CI simulation of an explicit external cache. In a real ephemeral runner, choose a registry or provider backend and use a distinct cache reference/scope per image and trust domain.
6. Test the built image before release publication
SOURCE_SHA=$(cat dca36-lab/evidence/source-sha.txt)
OUT=$(docker run --rm -e RELEASE=test -e SOURCE_SHA="$SOURCE_SHA" dca36-app:ci)
printf '%s
' "$OUT" | tee dca36-lab/evidence/test-output.txt
printf '%s
' "$OUT" | grep -F "source=$SOURCE_SHA"
This is an image-level smoke test. Real pipelines should layer unit/integration/security tests as appropriate, but every test record should identify the digest or build result it evaluated.
7. Record the loaded image identity
docker image inspect dca36-app:ci \
--format 'id={{.Id}} repoDigests={{json .RepoDigests}} labels={{json .Config.Labels}}' | tee dca36-lab/evidence/image-inspect.txt
cat dca36-lab/evidence/build-metadata.json
8. Optional local registry publication path
docker run -d --name dca36-registry --label academy.lab=dca36 -p 127.0.0.1:5000:5000 registry:2
docker tag dca36-app:ci 127.0.0.1:5000/dca36/app:build
docker push 127.0.0.1:5000/dca36/app:build | tee dca36-lab/evidence/push.log
docker buildx imagetools inspect 127.0.0.1:5000/dca36/app:build | tee dca36-lab/evidence/registry-build.txt
If loopback registry push is unavailable on your platform, stop here and use the OCI-layout simulation in the next section rather than changing daemon trust or adding a global insecure-registry setting.
9. Promote by registry metadata, not rebuild
docker buildx imagetools create --tag 127.0.0.1:5000/dca36/app:release-1 127.0.0.1:5000/dca36/app:build
docker buildx imagetools inspect 127.0.0.1:5000/dca36/app:release-1 | tee dca36-lab/evidence/registry-release.txt
imagetools create creates a new registry reference from
existing registry content. No Dockerfile build step occurs. Compare
the digest in the build and release inspection outputs.
10. Registry-cache architecture for ephemeral runners
Production pattern (not required by local lab):
--cache-from type=registry,ref=REGISTRY/ORG/app:buildcache-main
--cache-to type=registry,ref=REGISTRY/ORG/app:buildcache-main,mode=max
Fork/untrusted PR pattern:
read trusted cache only if policy permits
write to a separate non-release cache scope, or disable cache export
never give fork code release-registry credentials
11. Attestation path
# For a trusted registry that supports OCI attestations:
docker buildx build --builder dca36-ci --provenance=mode=min --sbom=true --push -t REGISTRY/ORG/app:ci SOURCE_DIR
This is a reference pattern, not a command to paste with literal
placeholders. Current BuildKit adds minimal provenance by default
for supported registry outputs; --sbom=true opts into
SBOM. In GitHub Actions, current Docker-maintained actions have
provider-specific provenance defaults. Bind evidence to the
resulting digest.
12. Exact cleanup
docker rm -f dca36-registry 2>/dev/null || true
docker image rm dca36-app:ci 127.0.0.1:5000/dca36/app:build 2>/dev/null || true
docker buildx rm dca36-ci
printf 'Keep dca36-lab/evidence until reviewed.
'
13. Challenge: choose the layer
Your release alias points to the expected digest, but a deployment node pulls a different digest. Which layer do you inspect first? Answer: registry/name-resolution and deployment reference evidence—not BuildKit cache. Confirm the exact repository, registry, platform, tag-to-index resolution and deployment configuration before rebuilding anything.
Knowledge check
Why is the builder explicitly named in every build command?
It prevents accidental use of another builder/context and binds cache/worker evidence to the intended CI worker.
What proves promotion did not rebuild?
The promotion operation manipulates registry references only, and the before/after registry inspections resolve to the same immutable digest.
Why is local cache moved from cache-next after the
build?
The local cache exporter writes a new cache directory; replacing the old directory only after success avoids mixing partial state.
Should you enable a global insecure registry just to complete this lab?
No. Use loopback if already supported or the simulation path; daemon trust changes are outside this disposable lab.
What should a forked PR be allowed to write?
Only a cache/output scope that cannot affect trusted release state or credentials; release publication should remain in a trusted job.
Official references and version notes
Lab baseline: 2026-09-22. The mandatory workflow
uses a disposable docker-container builder and local
cache. Registry and hosted-provider integrations are layered on top
without making paid/cloud features mandatory.
- 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.