Skip to content

feat(sandbox): add the guest image build and publish pipeline (sandbox Phase 3) - #199

Merged
quickbeard merged 2 commits into
mainfrom
sandbox-image-pipeline
Aug 12, 2026
Merged

quickbeard merged 2 commits into
mainfrom
sandbox-image-pipeline

Conversation

@quickbeard

Copy link
Copy Markdown
Owner

Adds the sandbox guest image build and publish pipeline (Phase 3), plus two fixes found while running it end to end.

Pipeline

Builds the guest images with Docker so the process is identical on macOS and Linux. Per arch it produces a bwrap rootfs tarball, a vz bundle of vmlinux + an ext4 rootfs, and a manifest.json matching src/manifest.ts.

  • image/ — one Debian guest consumed by every backend, a minimal virtio-only kernel, a tools image for ext4 conversion, and the system-wide codev-office install (skipped with --no-office)
  • script/build-image.ts — per-arch build, digest-pinned base, sparse-aware tarring, sha256 manifest assembly
  • script/publish-image.ts — versioned artifacts upload first, manifest.json last, so clients never see a version before its assets exist
  • script/e2e-vz.ts — boots a locally built vz image through codev-vmm and the vsock health check

Fixes

Partial publishes could retire the working manifest. manifest.json is deliberately unversioned — it is the pointer every client reads via SANDBOX_MANIFEST_URL, so publishing replaces it wholesale. Nothing stopped an arm64-only or 0.0.0-dev build from being published over it, which would leave the omitted platforms failing on every launch with sandbox manifest has no image for bwrap-x64. publish-image.ts now refuses a manifest missing any {bwrap,vz}-{arm64,x64} asset, and refuses a version that is not N.N.N, unless --force is passed.

Registry-facing steps had no retry. The prepull primes the local store, but buildkit still resolves the FROM remotely — a real run died on load metadata ... DeadlineExceeded with the base image already local. The pull and all three buildx builds now share one bounded retry; buildkit keeps completed layers, so a retry resumes rather than restarts. The comment claiming the prepull removes remote resolution was wrong and has been corrected.

Verification

A full arm64 image was built end to end on Apple Silicon with --no-office:

asset size sha256 verified
bwrap-arm64.tar.gz 231,481,992 B yes
vz-arm64.tar.gz 244,964,869 B yes

Both digests in the generated manifest.json match shasum -a 256 of the artifacts.

bun typecheck clean; bun test 61 pass / 2 skip / 0 fail.

The new guard was exercised against that real manifest and against synthetic ones:

  • arm64-only dev manifest → refused, has no bwrap-x64, vz-x64, exit 1
  • same with --force → proceeds to the upload dry run, exit 0
  • complete manifest at 0.0.0-dev → refused on the version, exit 1
  • complete manifest at 2026.08.1 → passes both guards and continues to the artifact check

Notes

  • Not tracked in DIVERGENCES.md: every file here is under the fork-only packages/codev-sandbox. No CHANGELOG.md entry, matching the three prior sandbox phase PRs.
  • The published image is arm64-only and office-less, so it is a pipeline smoke test, not a shippable release. A real release needs --arch x64 on a native x86_64 host and --office-bundle.
  • Latent, not fixed here: e2e-vz.ts calls hash.digest("hex") inside the resolveImage callback. resolveImage runs once today (only create() calls prepare), so it works, but a second invocation would throw Digest already called. Hoisting the digest out of the closure would make it robust.

🤖 Generated with Claude Code

quickbeard and others added 2 commits August 12, 2026 10:01
…x Phase 3)

Build the sandbox guest images with Docker so the pipeline runs the same on
macOS and Linux. Per arch it produces a bwrap rootfs tarball, a vz bundle of
vmlinux plus an ext4 rootfs, and a manifest.json matching src/manifest.ts.

- image/: one Debian guest consumed by every backend, a minimal virtio-only
  kernel, a tools image for ext4 conversion, and the system-wide codev-office
  install (skipped with --no-office)
- script/build-image.ts: per-arch build, digest-pinned base, sparse-aware
  tarring, sha256 manifest assembly
- script/publish-image.ts: uploads versioned artifacts first, manifest.json
  last, so clients never see a version before its assets exist
- script/e2e-vz.ts: boots a locally built vz image end-to-end through
  codev-vmm and the vsock health check

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… builds

manifest.json is deliberately unversioned because it is the pointer every
client reads, so publishing replaces it wholesale. Nothing stopped a
single-arch or dev-versioned build from retiring the working manifest for the
platforms it omits; publish-image.ts now refuses both unless --force.

build-image.ts prepulls the digest-pinned base, but buildkit still resolves
the FROM remotely and a real run died on "load metadata ... DeadlineExceeded"
even with the image local. Route the pull and the three buildx builds through
one bounded retry; buildkit keeps completed layers, so a retry resumes.

Also ignore the office bundle staging dir contents, which are build inputs.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@quickbeard
quickbeard merged commit 2e63e57 into main Aug 12, 2026
0 of 5 checks passed
@quickbeard
quickbeard deleted the sandbox-image-pipeline branch August 12, 2026 03:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant