Updating Images and Verifying Signatures
May 16, 2026 · View on GitHub
This guide explains how to refresh image digests, verify cosign signatures, and understand the three installation modes.
Install from a signed release tarball
The recommended production install path is the signed weekly release tarball, not a git clone. Every tarball is:
- Signed with cosign keyless (GitHub OIDC, Sigstore Rekor transparency log)
- Digest-attested with a
sha256sum-verifiable.sha256file - SLSA provenance attached as an in-toto attestation (SLSA Build L3)
- SBOM generated from the locked image set (CycloneDX JSON)
- Immutable per weekly tag — the tarball content is frozen at build time
The git-clone path (git clone ... && ./setup.sh) is fine for development and contribution. For any persistent or production deployment, prefer the tarball so you get the full verification chain.
Download the latest weekly release
# Resolve the latest weekly-* tag
TAG="$(gh release list --repo gl0bal01/devbox --json tagName,createdAt \
--jq 'sort_by(.createdAt) | last | .tagName')"
# Download tarball + all attestation files
gh release download "$TAG" --repo gl0bal01/devbox \
-p 'devbox.tar' -p 'devbox.tar.sha256' -p 'devbox.tar.sig' \
-p 'devbox.tar.pem' -p 'sbom.cdx.json' -p 'devbox.tar.intoto.jsonl'
Verify before extracting
Step 1 — SHA256 integrity check:
sha256sum -c devbox.tar.sha256
# Expected output: devbox.tar: OK
Step 2 — cosign keyless signature verification:
# Install cosign if needed: https://docs.sigstore.dev/system_config/installation/
cosign verify-blob devbox.tar \
--signature devbox.tar.sig \
--certificate devbox.tar.pem \
--certificate-identity 'https://github.com/gl0bal01/devbox/.github/workflows/weekly-rebuild.yml@refs/heads/main' \
--certificate-oidc-issuer 'https://token.actions.githubusercontent.com'
# Expected output: Verified OK
Both --certificate-identity and --certificate-oidc-issuer are required — omitting either would accept any GitHub Actions workflow as a valid signer.
Step 3 — SLSA provenance verification (optional, recommended for compliance):
# Install slsa-verifier: https://github.com/slsa-framework/slsa-verifier
slsa-verifier verify-artifact devbox.tar \
--provenance-path devbox.tar.intoto.jsonl \
--source-uri github.com/gl0bal01/devbox \
--source-tag "$TAG"
# Expected output: PASSED: SLSA verification passed
Extract and run
mkdir "devbox-${TAG}"
tar -xf devbox.tar -C "devbox-${TAG}"
cd "devbox-${TAG}"
# Review what setup.sh will do (no root required for dry-run):
./setup.sh --dry-run
# Install:
sudo ./setup.sh
The tarball bundles services/, scripts/, setup.sh, README.md, LICENSE, and docs/ — everything needed for a self-contained install with no network access to the repo.
Image Digest Refresh
Devbox uses minor-pinned tags (e.g., traefik:v3.2) with SHA digest locking for reproducibility. The update-images.sh script refreshes digests within the pinned minor version.
Check for Updates (Dry-Run)
cd ~/docker/devbox # Or wherever you cloned the repo
# Check if any digests have changed
./scripts/update-images.sh --check
echo "Exit code: $?"
Exit code 0 means the committed lockfiles match the latest resolved digests. Exit code 1 means updates are available.
Review Proposed Changes
# See exactly what would change
git diff HEAD -- services/*/docker-compose.lock.yml
# Or, after running --check, see the new lockfile:
cat /tmp/update-check-output.lock.yml | diff -u services/traefik/docker-compose.lock.yml - | head -50
Apply Updates
# Refresh all image digests within pinned minors
./scripts/update-images.sh --apply
# Review the changes
git diff services/
# If satisfied, commit
git add services/
git commit -m "Refresh image digests ($(date +%Y-%m-%d))"
# If something looks wrong, revert
git checkout HEAD -- services/
Major Version Bumps
Minor-pinned tags (e.g., traefik:v3.2) pull the latest patch within the minor. Major version bumps (v3 → v4) are manual edits.
Bump a Major Version
-
Edit the base compose file:
# Edit services/traefik/docker-compose.yml # Change: image: traefik:v3.2 # To: image: traefik:v4.1 -
Refresh digests to resolve the new version:
./scripts/update-images.sh --apply -
Test the new version locally (if possible):
docker compose -f services/traefik/docker-compose.yml \ -f services/traefik/docker-compose.lock.yml config | grep "image: traefik" # Or spin up a test environment: docker compose up --no-start # Create containers without starting docker compose up traefik # Start one service to test docker compose logs traefik # Watch for errors docker compose down # Cleanup -
Commit the changes:
git add services/ git commit -m "Bump traefik to v4.1 (breaking changes noted in v4.0 changelog)"
Installation Modes
Devbox supports three installation modes, with increasing trust requirements and reproducibility.
Mode 1: latest (Development Only)
Install from the latest commit on the main branch:
git clone https://github.com/gl0bal01/devbox.git
cd devbox
./setup.sh
What you get:
- Pulls the latest code from main
- Uses whatever image digests are currently committed
- No signature verification
- No smoke test
- NOT RECOMMENDED for production
When to use:
- Development and testing
- Contributing to devbox itself
- Rapid iteration
Risk:
- Digests may not have been smoke-tested before merge
- No provenance information
- Bleeding-edge (possible regressions)
Mode 2: weekly-YYYYMMDD (Recommended for Production)
Install from a tagged weekly release:
# List available weekly releases
git tag | grep weekly- | sort
# Checkout a specific weekly tag (e.g., weekly-20260413)
git checkout weekly-20260413
./setup.sh
What you get:
- Digests have been:
- Resolved from minor-pinned tags by CI
- Smoke-tested (all services start and pass health checks)
- Signed with cosign keyless (GitHub OIDC identity)
- Attested with SBOM (dependencies list)
- Attested with SLSA provenance (build environment)
- Git tag exists (immutable reference)
- cosign signature in the GitHub release
When to use:
- Production deployments (recommended)
- Persistent installs
- Any case where reproducibility matters
Risk:
- One week behind latest (by design, allows time for issues to surface)
- If the weekly release is bad, rollback to the prior week's tag
Mode 3: @sha256:<digest> (Most Paranoid)
Pin the release artifact by SHA256:
# Pin the digest manually (from a release you've previously verified)
DEVBOX_SHA256="abc123def456..."
TAG="weekly-20260516"
gh release download "$TAG" --repo gl0bal01/devbox -p 'devbox.tar' -p 'devbox.tar.sha256'
# Cross-check against your pinned digest
echo "$DEVBOX_SHA256 devbox.tar" | sha256sum -c
# Or: trust only the release-shipped .sha256
sha256sum -c devbox.tar.sha256
# Extract and install
mkdir devbox-${TAG} && tar -xf devbox.tar -C devbox-${TAG}
cd devbox-${TAG}
sudo ./setup.sh
What you get:
- Exact artifact verification by cryptographic hash
- Same guarantees as Mode 2 (weekly tag)
- Immutable reference (SHA is the artifact's permanent identity)
When to use:
- Maximum reproducibility
- Compliance requirements (e.g., financial, healthcare)
- Offline verification (compare SHA manually)
Risk:
- Requires manually tracking SHAs (harder to automate updates)
- Still depends on GitHub release integrity
Understanding the Cosign Identity Fields
The verification command shown earlier (in Install from a signed release tarball) requires two explicit flags:
-
--certificate-identity— the exact GitHub Actions workflow that signed this release.- Format:
https://github.com/ORG/REPO/.github/workflows/WORKFLOW.yml@refs/heads/BRANCH - Pins the signature to this specific workflow — a compromised different workflow cannot satisfy it.
- Format:
-
--certificate-oidc-issuer— the OIDC issuer that issued the signing certificate.- Always
https://token.actions.githubusercontent.comfor GitHub Actions keyless flows. - Proves the signer held a valid GitHub Actions OIDC token at signature time.
- Always
Omitting either flag makes the verification effectively useless — any keyless cosign signature from any GitHub Actions workflow would match.
Optional — Rekor transparency log lookup
# Search Rekor for entries signed by the weekly-rebuild workflow
rekor-cli search --rekor_server https://rekor.sigstore.dev \
--artifact devbox.tar
The Rekor entry is append-only and tamper-evident; a signature you've verified once can be re-verified offline at any time using the same cert + signature files shipped in the release.
Multi-Architecture (arm64) Caveat
Image digest lockfiles target the CI runner architecture (x86_64). If you're deploying on arm64:
First-Time Setup on arm64
# Clone and checkout the desired release
git clone https://github.com/gl0bal01/devbox.git
cd devbox
git checkout weekly-20260413
# The existing lockfiles are x86_64; regenerate for your architecture
./scripts/update-images.sh --apply
# This pulls images and resolves digests for your local arm64 architecture
# The lockfiles now contain arm64 digests
# Commit or store locally (don't push unless you want to bump the release)
# git add services/ && git commit -m "arm64 digests" # Optional
# Now install
./setup.sh
Subsequent Installs on arm64
The lockfiles are now arm64-specific. Weekly updates will continue to refresh arm64 digests:
git pull # Get the latest x86_64 lockfiles from main
git checkout weekly-YYYYMMDD # Check out the weekly tag (x86_64)
# Regenerate for arm64
./scripts/update-images.sh --apply
# Install
./setup.sh
Why this is necessary:
- Docker image manifest lists contain platform-specific digests (x86_64, arm64, etc.)
docker compose config --lock-image-digestsresolves to the current platform's digest- arm64 digests differ from x86_64 digests (different binaries, different dependencies)
- CI runs on x86_64; the committed lockfiles therefore contain x86_64 digests
Emergency: Sigstore Unavailability
If Sigstore Rekor is unavailable (rare), new signatures cannot be generated, but existing releases remain valid:
# If you're in the middle of an install and Sigstore is down:
# Option 1: Wait for Sigstore to recover (a few hours at most)
# Option 2: Use the fallback path (not recommended, see below)
# Fallback (untrusted):
DEVBOX_SKIP_SIGNATURE_VERIFY=1 ./setup.sh
# WARNING: This deploys code WITHOUT signature verification.
# Re-verify after Sigstore recovers and Rekor entry is available.
Important:
- Existing
weekly-YYYYMMDDreleases remain valid (their signatures were created at build time) - Only new releases would be blocked
- Use this override only in emergencies
- After Sigstore recovers, manually verify the cosign signature before deploying
Troubleshooting
update-images.sh --check exits 1
Cause: Image digests have changed (new patches available within the pinned minor).
Action: Run --apply, review the diff, and commit if desired.
SHA mismatch on fetch_and_verify
Cause: A development tool (bun, rustup, etc.) released a new version; the pinned SHA no longer matches.
Action:
# Option 1: Update the manifest (preferred)
./scripts/update-manifest.sh --apply
git add scripts/lib/download-manifest.sh
git commit -m "Update download manifest"
./setup.sh
# Option 2: Emergency override (not recommended)
DEVBOX_ALLOW_UNVERIFIED=1 ./setup.sh
# Then update the manifest before the next install
Traefik fails to start after digest update
Cause: The newly resolved Traefik digest is broken upstream (rare).
Action:
# Revert the digest update
git revert HEAD
# Redeploy from the prior commit
./start-all.sh
# Investigate what went wrong
docker logs traefik
# File an issue on the Traefik project if it's a known bug
Alpine backup image refresh
scripts/host/backup.sh exports named Docker volumes via a pinned alpine image declared in scripts/lib/devbox-contract.sh as ALPINE_BACKUP_IMAGE (sha256 digest form). Treating this as a contract value keeps backup output reproducible across operator workstations.
Auto-PR mechanism
.github/workflows/weekly-rebuild.yml includes an alpine-digest-check job that runs on the same weekly schedule. The job:
- Resolves the current
alpine:3.21multi-arch manifest digest viadocker manifest inspect. - Reads the digest currently pinned in
scripts/lib/devbox-contract.sh. - If they match, exits cleanly with no side effect.
- If they differ, bumps the digest in
devbox-contract.shand opens a pull request on a freshchore/alpine-digest-refresh-<run-id>branch.
The workflow never commits to main directly. Operators review the PR diff and merge (or close) on their own cadence.
Operator review checklist
When the PR opens:
- Confirm
scripts/lib/devbox-contract.shis the only file changed. - Run
docker manifest inspect alpine@sha256:<new-digest>locally to confirm the digest resolves on docker.io/library/alpine. - Make sure no other
ALPINEreferences slipped in (the bump is a pure digest swap). - Merge to ship the new image; close the PR to defer until the next weekly check.
Manual refresh fallback
If you need to refresh outside the weekly schedule:
# Resolve the current upstream digest
NEW_DIGEST="$(docker manifest inspect alpine:3.21 \
| jq -r '.manifests[0].digest // .config.digest // empty')"
echo "${NEW_DIGEST}"
# sha256:<64-hex>
# Replace the digest in the contract
sed -i -E "s|alpine@sha256:[a-f0-9]{64}|alpine@${NEW_DIGEST}|g" \
scripts/lib/devbox-contract.sh
# Verify the contract still passes Bats
bats tests/contract/contract.bats
# Commit
git add scripts/lib/devbox-contract.sh
git commit -m "chore(contract): refresh ALPINE_BACKUP_IMAGE digest"
References
- ARCHITECTURE.md: Digest pinning strategy
- ARCHITECTURE.md: Tag pinning and version bumps
- ARCHITECTURE.md: Cosign keyless verification
- docs/security.md: Trust model and threat boundaries
- docs/ops.md: Backup and incident response