Docker in CI/CD: Reproducible Builds, Buildx, Registry Caching, Ephemeral Runners, and Release Promotion: Concepts, Architecture, and Mental Model
Design CI/CD around exact source revisions, ephemeral trusted builders, scoped caches, immutable image digests, attestations, and promotion of the same bytes instead of release-time rebuilds.
Learning objectives
- Explain why a CI release must be bound to one exact source revision and one immutable image or index digest.
- Trace source SHA through builder identity, cache scope, image digest, tests, attestations, registry publication, promotion alias, and runtime verification.
- Distinguish build completion from image publication, test success, attestation creation, promotion, and deployed identity.
- Explain why ephemeral runners and scoped caches reduce cross-job state leakage without making cache content inherently trustworthy.
- Define the evidence required to prove that release promotion reused the original bytes instead of rebuilding them.
1. The practical problem: “the pipeline passed” is not an image identity
A CI system can report success while still leaving an ambiguous release. A mutable tag may move after testing. A deployment job may rebuild from the same branch but obtain a newer base image. A shared cache may inject unreviewed state. A release alias may be updated before the registry has the intended index. The cure is to make the immutable digest—not the job name or tag—the release subject.
The operating rule for this chapter is build once, verify that digest, then promote the same digest. If a later stage rebuilds, it has created a new artifact and must repeat the verification chain.
2. Causal model: source revision to runtime identity
flowchart TD
A[Exact source SHA] --> B[Ephemeral trusted builder]
B --> C[BuildKit graph + scoped cache]
C --> D[Image/index digest]
D --> E[Tests + SBOM + provenance]
E --> F[Registry subject digest]
F --> G[Promotion alias / environment]
G --> H[Runtime pull + digest verification]
D --> I[Evidence packet]
E --> I
F --> I
H --> I
The source SHA fixes the repository state. The builder identity and versions describe who/what executed the build. The cache can accelerate execution but must not replace source or base-image identity. The image/index digest names the resulting bytes. Tests and attestations describe properties of that digest. Promotion changes a human-facing alias or environment pointer, not the digest. Runtime verification closes the loop by proving what was actually pulled or started.
3. Separate state domains before automation
| State domain | Examples | Evidence |
|---|---|---|
| Source | Git commit, dirty-tree state, workflow definition | full SHA; clean/dirty status; workflow/run ID |
| Builder | Buildx version, BuildKit worker, driver, platforms |
docker buildx version;
buildx inspect --bootstrap
|
| Inputs | Dockerfile, context, base references, secrets IDs | source checksum; base digests; secret IDs only, never values |
| Cache | local/registry/GHA backend and scope | cache ref/scope; import/export logs; trust domain |
| Artifact | image or multi-platform index | registry repository + immutable digest + platforms |
| Evidence | tests, SBOM, provenance, signature if used | reports bound to subject digest |
| Promotion | release/environment alias | before/after alias target digest |
| Runtime | deployed/pulled object | RepoDigest/index digest/container image ID where applicable |
5. Buildx and BuildKit in ephemeral CI
Ephemeral runners intentionally discard local daemon and filesystem state after a job. BuildKit solves the performance cost by exporting cache to an explicit backend and importing it on the next run. Current Buildx supports local, inline, registry and GitHub Actions cache backends in common configurations. The cache key/scope is part of the security design: caches shared across forks or unrelated trust domains can become a supply-chain input.
6. Read-only preflight first
set -eu
printf 'context=%s
' "$(docker context show)"
docker version
docker buildx version
docker buildx ls
docker buildx inspect --bootstrap
Record the actual installed versions instead of assuming the course baseline. In 2026-09-22 upstream Buildx 0.37.1 and BuildKit 0.33.0 are current, but Docker Desktop, distro packages, CI images, and managed builders may package different versions.
7. Cache is an optimization input, not release identity
| Cache pattern | Strength | Primary risk |
|---|---|---|
| Runner-local | fast on persistent trusted runner | state disappears on ephemeral runner; cross-job leakage on shared runner |
| Local exported directory | simple/free and auditable path | must scope path and lifecycle per trust domain |
| Registry cache | portable across ephemeral runners | registry auth, overwrite scope, retention and cache poisoning need policy |
| GitHub Actions cache | convenient hosted CI integration | provider limits and scope semantics; provider-specific trust boundary |
8. Build, test, publish and promote are different verbs
- Build: produce an image/index from declared inputs.
- Test: execute checks against that exact result or a digest-equivalent pull.
- Publish: store the result under a registry repository and capture its digest.
- Attest: attach provenance/SBOM claims to the digest.
- Promote: move a release/environment alias to the already-tested digest.
- Deploy: pull/run and verify the digest in the target environment.
9. Current attestation behavior matters
BuildKit creates minimal provenance by default for supported registry outputs. SBOM is opt-in. Attestations are attached to an image index and survive when pushed to a registry; loading into a classic local image store can lose or reject them. Docker-maintained GitHub Actions additionally have provider-specific provenance defaults. Never pass secrets through build arguments: provenance may intentionally record build parameters.
10. Multi-platform releases have two digest levels
A multi-platform tag resolves to an image index digest, which in turn references per-platform manifest digests. Promotion normally targets the index digest so all tested platforms move together. If only one platform is promoted, record that narrower subject explicitly.
11. Evidence ledger
| Field | Example meaning |
|---|---|
| source_sha | exact commit that triggered the build |
| run_id | CI/local simulation execution identity |
| builder | name, driver, Buildx/BuildKit versions |
| cache_scope | backend + namespace/reference + trust domain |
| subject_digest | immutable image/index digest |
| test_result | pass/fail report linked to subject |
| attestations | SBOM/provenance presence and mode |
| promotion | alias before/after digest |
| runtime | pulled/deployed digest verification |
12. Safe baseline for this chapter
- Never publish only
latestas release evidence. - Never rebuild in a deployment stage and call it promotion.
- Never share write-capable caches across untrusted forks by default.
- Never use build arguments for secrets; use BuildKit secret/SSH mounts.
- Keep runner/builder credentials short-lived and scoped.
- Delete only exact disposable builders, registry containers and lab directories.
Knowledge check
Why is a release tag insufficient as artifact identity?
Because a tag is mutable registry metadata; the immutable digest identifies the exact image/index bytes.
What does “build once, promote many” prohibit?
Rebuilding at staging or production promotion time. A rebuild creates a new digest that needs its own tests and evidence.
Why can a cache be a security boundary?
Cached build records can influence future output; sharing cache write/read scope across untrusted jobs creates a cross-job supply-chain input.
Does successful buildx build prove the registry
contains the intended digest?
No. Publication and registry digest verification are separate states.
What is the normal promotion subject for a multi-platform image?
The image index digest, unless the process explicitly promotes a single platform manifest.
Official references and version notes
2026-09-22. Upstream Buildx 0.37.1 and BuildKit 0.33.0 are current. Docker’s current cache documentation lists inline/local/registry/GHA backends in common use; minimal provenance remains the BuildKit default on supported image outputs, while SBOM is opt-in.
- 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.