feat(image): ship a reference sandbox image, and publish it from CI - #1
Merged
Conversation
openblox pulls an absent image on create, but never shipped one, so the default path did not work until you had authored a Dockerfile yourself. image/ is that default: python3, bash, nc and a non-root user on Debian slim. The three things openblox actually requires of an image — a shell, a non-root USER, and nc-or-python3 for the preview relay — are now stated in one place and asserted at build time, rather than being folklore that surfaced as a broken preview. Debian rather than Alpine because musl has no manylinux wheels, so on Alpine every numpy install compiles from source inside the sandbox. CI publishes it to ghcr: a tag pushes that version plus :latest, main pushes :edge, both multi-arch. A pull request builds and asserts the contract without pushing, so a broken Dockerfile is a red check rather than a missing image discovered on main. The digest lands in the job summary, because a tag can be repointed by whoever controls the registry and the image is the sandbox's entire userland. Verified against a real gVisor host: create, python3, bash, non-root, /workspace round-trip, a detached process, and a preview fetched over the relay — which now takes the nc fast path rather than the python3 fallback.
Lutherwaves
added a commit
that referenced
this pull request
Aug 17, 2026
openblox pulls an absent image on create, but never shipped one, so the default path did not work until you had authored a Dockerfile yourself. image/ is that default: python3, bash, nc and a non-root user on Debian slim. The three things openblox actually requires of an image — a shell, a non-root USER, and nc-or-python3 for the preview relay — are now stated in one place and asserted at build time, rather than being folklore that surfaced as a broken preview. Debian rather than Alpine because musl has no manylinux wheels, so on Alpine every numpy install compiles from source inside the sandbox. CI publishes it to ghcr: a tag pushes that version plus :latest, main pushes :edge, both multi-arch. A pull request builds and asserts the contract without pushing, so a broken Dockerfile is a red check rather than a missing image discovered on main. The digest lands in the job summary, because a tag can be repointed by whoever controls the registry and the image is the sandbox's entire userland. Verified against a real gVisor host: create, python3, bash, non-root, /workspace round-trip, a detached process, and a preview fetched over the relay — which now takes the nc fast path rather than the python3 fallback.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
openblox pulls an absent image on create, but never shipped one — so the default path didn't work until you'd authored a Dockerfile yourself. This adds that image and the CI to publish it.
The image
image/Dockerfile→ghcr.io/blox-eng/openblox-sandbox. Debian slim +python3,bash,nc, non-rootsandboxuser (uid 1000), writable/workspace. 121 MB.Debian rather than Alpine deliberately: sandboxes run Python, musl has no manylinux wheels, so on Alpine every
numpy/pandasinstall compiles from source inside the sandbox.The contract, written down
openblox requires exactly three things of an image, and until now they were folklore that surfaced as a broken preview:
/bin/sh— openblox replaces the entrypoint with its own idle loopUSER— an integration test already asserts thisncorpython3— the preview relay reaches an in-sandbox port over the exec channel, since the container has no network interfaceNow stated in
image/README.md, asserted at build time, and re-asserted against the pushed manifest.CI
:edge, multi-archv*:X.Y.Z+:latest, multi-archThe digest goes to the job summary — pin that, not a tag: a tag can be repointed by whoever controls the registry, and the image is the sandbox's entire userland.
The PR job exists so a broken Dockerfile is a red check rather than a missing image discovered on main.
Verification
Built locally and driven through the real openblox API on a gVisor host:
The preview now takes the
ncfast path instead of the python3 fallback.Also drops a stale "Not yet published" line from the README.
Follow-up (not in this PR)
Blox's own
blox-sandboximage is stillFROM daytonaio/sandbox. Rebasing it onto this image is what finally removes that dependency — and is whyncwas missing in the first place.