boxkite

Capability

Custom sandbox images

The declarative builder lets you compose a custom sandbox image instead of using the operator's default — pick a pre-approved base, add exact-version-pinned python_packages/apt_packages/npm_packages, and get back an image_id to pass into sandbox creation once the build reaches completed. Not a Dockerfile-passthrough API — there is no caller-influenced FROM, ENTRYPOINT, or arbitrary RUN, only a pinned package list layered on a fixed base.

Opt-in, and not yet cluster-hardened for untrusted multi-tenant traffic

This whole capability is off by default (BOXKITE_IMAGE_BUILDER_ENABLED=false) — every /v1/images route 404s until an operator explicitly opts in. The build-job spec and dispatch logic are implemented and covered by tests, but the real Kubernetes build execution (KanikoJobBuildRunner.run_build) does not yet run against a live cluster. Don't treat this as production-hardened.

The project's own design doc and SECURITY.md both recommend a dedicated security review before enabling this against real, untrusted multi-tenant traffic — building an image is a materially different trust boundary than running one. Read docs/DECLARATIVE-BUILDER-DESIGN.mdand the "New trust boundary: the declarative builder" section of SECURITY.md before turning this on.

Choose a base

base is restricted to an enum of pre-approved, digest/tag-pinned images — never a free-form image reference:

  • "boxkite-default"— the full data-science/document/browser stack, same as the operator's default sandbox image.
  • "boxkite-minimal" — a lean python+node base with none of that preinstalled, for composing a package set from scratch on a smaller image.
  • "boxkite-node" — genuinely Python-free (no interpreter at all, not just an empty python_packages), for pure JS/TS workloads. Rejects python_packages.
  • "boxkite-go" — genuinely Python- and Node-free, for pure Go workloads. Rejects both python_packages and npm_packages.
  • "boxkite-rust"— genuinely Python- and Node-free, for pure Rust workloads (rustc/cargo from Wolfi's own signed apk package, not rustup). Rejects both python_packages and npm_packages.

Build

Every entry in python_packages/apt_packages/npm_packages must be exact-version pinned (name==version, or @scope/name==version for scoped npm packages) — a range or latest is rejected. The build is always asynchronous: the call below returns immediately with a queued image, not a finished one.

build_image.py
image = client.create_image(
    label="data-eng-with-polars",
    base="boxkite-default",
    python_packages=["polars==1.9.0", "duckdb==1.1.3"],
)
print(image["id"], image["status"])  # "img_...", "queued"

# Or, on boxkite-minimal/boxkite-node: bake in an npm-installed CLI
claude_image = client.create_image(
    base="boxkite-minimal",
    apt_packages=["git==2.54.0-r0", "openssh-client==10.0_p1-r2"],
    npm_packages=["@anthropic-ai/claude-code==2.0.1"],
)

Poll status, then use it

status progresses queued buildingscanningcompleted (or failed/rejected at any stage — a build that fails its vulnerability-scan gate is rejected, never silently promoted). Once completed, pass the image_idinto sandbox creation. Omit it and you get the operator's default image, exactly as before — an unowned or not-yet-completed image_id404s rather than silently falling back, so you can never mistakenly believe you're running your reviewed package set while actually running the shared default.

use_image.py
image = client.get_image(image["id"])
if image["status"] == "completed":
    sandbox = client.create_sandbox(label="polars-job", image_id=image["id"])

List and delete

Deleting an image only removes the control-plane's bookkeeping row — it does not retroactively tear down any already-running sandbox created from that image's digest.

manage_images.py
client.list_images()
client.delete_image(image["id"])