Reproducible Builds
Purpose: For platform engineers, explains deterministic builds, pinned versions, and state management.
Concept Summary
A reproducible build means running opencenter-airgap build twice with the same versions.env produces functionally identical Zarf artifacts. This matters in air-gapped environments because there is no way to fetch a missing dependency at deploy time — what you build is exactly what you get.
How Reproducibility Works
Single source of truth: versions.env
Every version in the build traces back to config/versions.env. This file pins:
- Kubernetes and container runtime versions (
KUBERNETES_VERSION,CONTAINERD_VERSION,RUNC_VERSION) - CNI plugin version (
CALICO_VERSION) - Platform service versions (
FLUXCD_VERSION,CERT_MANAGER_VERSION, etc.) - Tool versions (
HELM_VERSION,K9S_VERSION,YQ_VERSION) - Terraform provider versions (
OPENSTACK_PROVIDER_VERSION, etc.) - Git repository refs (
KUBESPRAY_COMMIT_SHA,OPENCENTER_GITOPS_BASE_COMMIT_SHA)
When build runs, it reads versions.env and generates components.yaml with resolved URLs and version strings. No version is inferred or defaulted at build time.
Commit SHA pinning for Git repositories
Branch names like main or master are mutable. For reproducibility, pin to a specific commit SHA:
# versions.env
KUBESPRAY_VERSION="master"
KUBESPRAY_COMMIT_SHA="a1b2c3d4e5f6"
OPENCENTER_GITOPS_BASE_VERSION="main"
OPENCENTER_GITOPS_BASE_COMMIT_SHA="f6e5d4c3b2a1"
The build clones at the specified SHA, not the branch tip. This ensures the same source code is used regardless of when the build runs.
Image tags vs. digests
Container image tags can be overwritten (e.g., latest, or even semver tags on some registries). For maximum reproducibility, use digest-pinned references where possible:
# components.yaml — tag-based (default)
- image: registry.k8s.io/kube-apiserver:v1.34.3
# components.yaml — digest-pinned (stronger guarantee)
- image: registry.k8s.io/kube-apiserver@sha256:abc123...
The scan command records tags as discovered. You can manually replace tags with digests in components.yaml for critical images.
What Can Break Reproducibility
| Factor | Risk | Mitigation |
|---|---|---|
| Mutable Git branch refs | Different code at different times | Pin COMMIT_SHA in versions.env |
| Mutable image tags | Tag overwritten with different content | Use digest references for critical images |
| Upstream URL changes | Download URL returns 404 or different binary | Cache build artifacts; verify checksums |
| OS package repo updates | Different package versions in apt mirror | Pin package versions in Kubespray config |
| Build tool version drift | Different CLI version produces different output | Pin opencenter-airgap version in pyproject.toml |
Build State and Checkpoints
The CLI tracks build progress in a state file. Each phase records:
- Start and end timestamps
- Input checksums (versions.env hash, components.yaml hash)
- Output artifact counts and sizes
This state enables --resume and also serves as an audit trail. If versions.env changes between runs, the state file detects the drift and forces a rebuild of affected phases.
Verifying Reproducibility
To confirm two builds are equivalent:
# Build once
opencenter-airgap build
sha256sum dist/zarf-package-*.tar.zst > build1.sha256
# Clean and rebuild
opencenter-airgap clean
opencenter-airgap build
sha256sum dist/zarf-package-*.tar.zst > build2.sha256
# Compare
diff build1.sha256 build2.sha256
Byte-identical output depends on all upstream artifacts being unchanged. If an upstream registry serves a different layer for the same tag between builds, the checksums will differ — this is the case for digest pinning.
Trade-offs
- Digest pinning adds maintenance overhead: you must update digests when upgrading versions.
- Commit SHA pinning requires looking up the SHA for each release, rather than using a branch name.
- Caching build artifacts locally (for offline rebuilds) consumes additional disk space.
The trade-off is worth it in regulated environments where you need to prove that the artifact deployed in Zone C matches exactly what was built and audited in Zone A.
Further Reading
- Building Packages — the build workflow
- Verifying Packages — checksum and SBOM verification
- Manifest Schema —
components.yamlfield definitions