Reproduce Builds Locally
Target audience: Developers, Security auditors Goal: Verify bit-for-bit reproducibility of StageX builds.
Prerequisites
- Podman (or Docker) installed
- git, curl, jq
- GPG (for signature verification)
- Hardware: see System Requirements for disk, RAM, and CPU specs by workload
Clone the StageX Repo
git clone --depth=1 https://codeberg.org/stagex/stagex.git
cd stagex
Release branches follow release/YYYY.MM.<revision>:
git branch --list 'release/*'
git checkout release/2026.05.0
Replace release/2026.05.0 with the latest available tag.
Preseed the Build Cache
Pull the last published OCI layers into out/ to seed the build cache without rebuilding from source:
make preseed
make preseedrunsdocker pullinternally (preseed.sh line 11). If using Podman, setENGINE=podmanor symlinkdocker→podman.
This downloads published images and extracts them into out/<package>/index.json plus blob directories. Subsequent incremental builds reuse cached layers.
To build from source instead (no preseed), skip this step and go directly to Build.
Build a Single Package
Build pallet-rust without building the full tree:
make pallet-rust
Output lands in out/pallet-rust/:
out/pallet-rust/
├── index.json
└── blobs/
└── sha256/
├── <digest-1>
└── <digest-2>
The first run also generates out/targets.mk via src/targets.py, which defines the build rules.
Get the Output Digest
Extract the image manifest digest — this is what gets published in digests/pallet.txt:
INDEX_DIGEST=$(jq -r '.manifests[0].digest | split(":")[1]' out/pallet-rust/index.json)
LOCAL_DIGEST=$(jq -r '.manifests[0].digest | split(":")[1]' "out/pallet-rust/blobs/sha256/$INDEX_DIGEST")
echo "$LOCAL_DIGEST"
This follows the index reference to the multi-arch manifest and retrieves the sha256 digest of the built image — the same logic used by src/digests.py.
Compare Against Published Digests
PUBLISHED_DIGEST=$(curl -s https://codeberg.org/stagex/stagex/raw/branch/main/digests/pallet.txt \
| grep pallet-rust | awk '{print $1}')
echo "Published: $PUBLISHED_DIGEST"
echo "Local: $LOCAL_DIGEST"
Check for a match:
[ "$LOCAL_DIGEST" = "$PUBLISHED_DIGEST" ] && echo "MATCH" || echo "MISMATCH"
Expected format (digests/pallet.txt):
<64-hex-chars> pallet-rust
A MATCH means your build is bit-for-bit identical to the published release — you've independently reproduced the maintainer's build.
Verify with Published Signatures
Once your local digest matches the published digest, verify the GPG signatures on that digest as described in Verify Multi-Signature Attestations.
Cross-Machine Verification
Build on a different machine using the same release branch and compare digests. StageX maintainers build across diverse hardware (MAINTAINERS):
| Maintainer | Hardware |
|---|---|
| Lance Vick | AMD Ryzen Threadripper 2990WX, AMD EPYC 7502P |
| Danny Grove | Intel Core i7-6700K, AMD Ryzen 7 7840U |
| Anton Livaja | AMD Ryzen Threadripper 2970WX, AMD EPYC Milan |
All produce identical digests for the same release.
Troubleshooting
| Problem | Cause | Fix |
|---|---|---|
| Build fails mid-way | Missing source tarball | make fetch && make pallet-rust |
command not found: docker in preseed |
preseed.sh hardcodes docker pull |
alias docker=podman or install docker-compatible CLI |
command not found: jq |
jq not installed | apt install jq or brew install jq |
| Out of disk space | out/ contains old builds |
rm -rf out/ and rebuild |
| Digest mismatch after preseed | Preseed pulled wrong release | git log --oneline to confirm branch; re-preseed |
No rule to make target 'pallet-rust' |
out/targets.mk not yet generated |
Run make pallet-rust once — targets auto-generate on first invocation |
| Signature verification fails | Missing maintainer GPG keys | See Verify Multi-Signature Attestations |