The CubeSandbox audit closes one parity story: every feature Tencent’s CubeSandbox README, docs, and examples directory advertise has a measured row on Coppice. This page closes the other side — the broader market of agent-sandbox products. Fly’s Sprites, E2B’s hosted cloud, Modal, Cloudflare’s Sandbox SDK, and Daytona each ship features the Cube spec doesn’t anticipate, and some of those features are real signals about what an honest self-hostable substrate should ship next.
Three buckets below: out of scope by design (a single-host substrate cannot, and should not pretend to, ship anycast or multi-region), partial (the primitive is in Coppice but the policy or the UX isn’t), and open in-scope (an honest gap, with a one-phrase sketch of what closing it would take). Sources sit at the bottom.
Refresh, 2026-04-26: the Cube-shaped rows for nginx BYOI,
OpenAI Agents, Linux bhyve guests, and
browser VS Code/code-server are now closed in
the Cube audit.
NVIDIA GPU passthrough is also now measured on honor: a Debian bhyve
guest sees the RTX 3070 Mobile/Max-Q assigned through ppt(4)
and the receipt ends in GPU_OK. GPU memory snapshots remain
a distinct Modal-shaped gap. E2B-shaped idle pause and AutoResume are
now wired for jail-backed sandboxes: timeoutMs plus
lifecycle.onTimeout=“pause” causes the reaper to pause
on expiry, and SDK/envd/files/commands activity resumes the jail.
Receipt rig:
benchmarks/rigs/auto-suspend-resume-smoke.sh.
Modal-shaped scheduled sandboxes are now wired as a single-gateway
interval scheduler: POST /schedules stores a nested
sandbox-create payload, launches future sandboxes through the normal
create path, and exposes runCount,
lastSandboxID, and cleanup via GET/DELETE
/schedules/:id. Receipt rig:
benchmarks/rigs/scheduled-sandboxes-smoke.sh.
The remaining VS Code-shaped market gap is narrower: local
VS Code Remote-SSH or JetBrains Gateway attach into a sandbox, with
expiring credentials and generated SSH config. That is different from
running a VS Code-compatible editor inside the browser, which Coppice
already does.
Out of scope by design
Single-host self-hosted means we trade global mobility for local ZFS, signed templates, and a 17 ms resume. The hosted competitors pay for cross-region with object-store persistence layers. That is the right trade for them and the wrong trade for us; it does not appear here as a gap because chasing it is the same as becoming a different product.
| market feature | advertised by | why we don’t ship it |
|---|---|---|
| Cross-host / multi-region mobility | Sprites, E2B, Modal, Daytona, Cloudflare | Coppice runs on the metal you point it at. The thing that makes our resume cheap (a local ZFS pool with the snapshot already on disk) is exactly what you give up the moment the substrate has to relocate. /appendix/cluster-overlay sketches a ZFS send/recv + VXLAN bridge for fleets that want it; “global anycast” is not on that path. |
| Anycast / edge placement of sandboxes | Sprites, Cloudflare | Requires a fleet you don’t run. Coppice is the substrate for a fleet you do run. |
| Multi-host autoscale to 50 000+ sandboxes | Modal | Single-host density is our axis; we get 1 000 microVMs into 9.1 GiB of RAM on a laptop. A scheduler over many laptops is a different product (and an interesting future one — tracked at cluster-overlay). |
| Hosted billing / invoicing / Stripe funnel | all four hosted competitors | Self-hosted means no upstream meter. We expose Prometheus counters per sandbox (per-sandbox-metrics) so you can wire your own billing if you want one. |
Out-of-scope items are not “missing” — they are the price of the trade Coppice exists to make.
Partial — primitive shipped, policy or UX missing
These are features where Coppice has the underlying mechanism but not the surface a competitor sells.
| market feature | advertised by | what we have / what’s missing |
|---|---|---|
| Public preview URLs with per-port signed tokens | Cloudflare, Modal, Sprites | Closed for the single-node gateway. L7 fan-out is shipped — <port>-<shortid>.<domain> via wildcard-dns and tools/coppiceproxy. Token-baking into the hostname is in coppiceproxy: when -secret is set, routes require <port>-<id>-<expires>-<hmac>.<domain>, and /_coppice/preview-url mints a signed host behind an optional admin bearer. The gateway also exposes GET /sandboxes/:id/preview-url?port=… using the same HMAC shape, and the admin machine rows can mint the URL. Receipts: benchmarks/rigs/signed-preview-url-smoke.sh and benchmarks/results/gateway-preview-url/latest.txt. Tenant/member-only auth remains part of the future hosted-auth layer, not the single-node primitive. |
| Snapshot fork-tree visualization | Daytona | Closed. Fork primitive is shipped — POST /snapshots/:id/fork. Forked sandboxes carry coppice_forked_from_snapshot_id, coppice_forked_from_source_sandbox_id, and coppice_forked_from_zfs_snapshot metadata, which /sandboxes exposes. Gateway startup now recovers live jail records from jls, rehydrates the allocator/epair map, infers fork lineage from the ZFS origin e2b-<source>@snap-<id>, and the admin dashboard joins that with /snapshots data. Receipt: benchmarks/results/snapshot-fork-restart-recovery/latest.txt. |
| Concurrency caps tied to a tenant | E2B | Closed for the gateway create path. API keys carry tenantID, middleware enforces coarse read, exec, and admin route scopes, and authenticated creates now get a reserved coppice_tenant_id metadata marker. COPPICE_TENANT_MAX_SANDBOXES applies a global running/paused cap per tenant; COPPICE_TENANT_SANDBOX_LIMITS overrides individual tenants and supports a * fallback. Durable org/key management and hosted billing policy remain product hardening. |
| State-preserving pause/resume across every backend | E2B | Closed for the single-node substrates that exist today. Jail-backed sandboxes support idle pause and AutoResume with live process memory preserved by SIGSTOP/SIGCONT on the same host; bhyve SSH guests and host-console guests now expose gateway-level pause/resume via owner-preserving pool commands. Paused bhyve pool entries stay owned and count as in-use capacity, so the reconciler does not overfill or hand them to another caller. Receipts: mise run lifecycle:auto-suspend-resume-honor and mise run lifecycle:bhyve-pause-resume-honor. |
| CPU memory snapshot for jails (skip import / JIT cost) | Modal | For bhyve guests we have it via bhyvectl —suspend + the vmm-vnode patch. For jails we don’t — jail “memory” is just the per-process page cache. Closing this for jails is either criu-equivalent (FreeBSD has no upstream criu) or accepting that jail boot already lands in <20 ms cold. |
| GPU passthrough sandboxes | Modal | Ordinary passthrough is no longer a gap. The bhyve template sidecar declares TPL_EXTRA_BHYVE_SLOTS=‘20:0,passthru,ppt0;21:0,passthru,ppt1’, honor binds the NVIDIA GPU and HDA function to ppt(4), and benchmarks/results/gpu-passthrough/latest.txt captures a Debian bhyve guest checkout plus in-guest NVML probes: nvidia-smi -L must show a real GPU N: row, then the rig records driver/memory/utilization and one pmon sample before GPU_OK. The receipt currently uses a 2 GiB guest memory window because 4 GiB exposed AMD-Vi IO_PAGE_FAULT / RmInitAdapter failures, and it drains after the probe instead of claiming same-device reset/recycle. Treat the remaining work as product hardening: CUDA kernel receipts, portal template polish, reset behavior, and GPU memory snapshots as a separate Modal feature. |
Open in-scope — honest gaps
These are features that are real for a self-hosted substrate to ship, that no Coppice primitive yet covers, and that we can scope in a phrase.
| market feature | advertised by | what closing it takes |
|---|---|---|
| Web dashboard — live stdout, kill, template manager | E2B, Modal, Daytona, Cloudflare | Closed for the single-node operator surface. The React demo portal (/ui/) operates individual sandboxes; the Astro admin dashboard (/admin/ on the site dev server) now renders live gateway probes, running machines, template/pool state, Grafana-lite Prometheus charts, lifecycle/webhook rows, snapshot lineage, VNET diagnostic health, and machine actions for opening, snapshotting, forking, preview URL minting, and Remote-SSH access. Local development uses Vite proxying to COPPICE_ADMIN_GATEWAY (default http://127.0.0.1:3001). Remaining hardening is production auth/hosting polish, not missing substrate data. |
| Per-team API keys with scoped permissions | E2B | Mostly closed for a self-hosted gateway. COPPICE_API_KEYS / COPPICE_API_KEYS_FILE enable gateway-wide API-key enforcement on both the API and envd surfaces, accepting X-API-Key or Authorization: Bearer and mapping keys to tenantID plus scopes through /auth/whoami. Middleware enforces coarse read, exec, and admin route classes; tenant create quotas are configurable through COPPICE_TENANT_MAX_SANDBOXES and COPPICE_TENANT_SANDBOX_LIMITS. /audit/events exposes mutating requests, auth failures, scope denials, and HTTP errors, with optional JSONL persistence through COPPICE_AUDIT_LOG. Receipt: benchmarks/results/api-key-auth/latest.txt. Remaining work: durable org/key management and hosted key-issuance UX. |
| Lifecycle event webhooks | E2B | Closed for the single-node gateway: GET /events/sandboxes and GET /events/sandboxes/:id return E2B-shaped created, paused, resumed, updated, snapshotted, and killed records with limit, offset, orderAsc, and repeated types= filters. /events/webhooks registers lifecycle sinks, signs outbound POSTs with E2B-compatible headers, retries failed deliveries, and exposes delivery attempts for the admin dashboard. Receipts: mise run lifecycle:events-honor, mise run lifecycle:webhooks-honor. Durable webhook storage and replay remain hardening work. |
| Object-store FUSE mount inside a sandbox | E2B, Cloudflare, Daytona | ”Mount this S3 / R2 / GCS bucket at /data.” Closing this is s3fs-fuse or rclone mount baked into a template + a route to thread credentials in. The interesting question is whether we want a bucket-mount or a Coppice-native object-store-backed dataset (a la JuiceFS / Sprites) — the latter is a much bigger lift. |
| GPU memory snapshots | Modal | Distinct from GPU passthrough. Closing this means proving what state survives a bhyve suspend/resume with an assigned GPU, then measuring restart latency for a CUDA workload. The first milestone is ordinary passthrough; GPU memory checkpointing comes only after the device-reset and driver-state behavior is measured. |
| Scheduled / cron sandboxes | Modal | Closed for the single-node gateway. POST /schedules accepts intervalSeconds, optional maxRuns, and a nested sandbox create payload, then launches runs through the same create path used by direct API calls. GET /schedules exposes runCount, failureCount, nextRunAt, lastSandboxID, and lastError; DELETE /schedules/:id cancels future launches. Receipt target: benchmarks/results/scheduled-sandboxes/latest.txt. Durable schedule persistence is still future hardening, not the parity primitive. |
| Hot-mount image into a running sandbox | Modal (mount_image) | Closed for jail-backed sandboxes as live volume hot-mounts. POST /sandboxes/:id/mounts accepts the same {name,path,readonly} shape used at create time, nullfs-mounts the registered ZFS volume into the running jail, records the live mount in the volume registry, and teardown unmounts it before destroying the sandbox clone. Receipt target: benchmarks/results/live-volume-mount/latest.txt. Raw image-file mounting remains a separate UX wrapper over the same ZFS/nullfs primitive. |
Subtree snapshot (snapshot_directory) | Modal | Closed as a tar+hash subtree artifact. POST /sandboxes/:id/directory-snapshots tars the requested absolute path inside the running sandbox, stores it under /tmp/coppice-directory-snapshots, and returns snapshotID, archivePath, sizeBytes, and sha256. POST /sandboxes/:id/directory-snapshots/restore restores that artifact into a target directory. Receipt target: benchmarks/results/directory-snapshot/latest.txt. Dataset-per-directory remains a future optimization, not the parity primitive. |
| First-class secrets store | Modal, Cloudflare | Closed for the single-node gateway. POST /secrets, GET /secrets, GET /secrets/:name, PUT /secrets/:name, and DELETE /secrets/:name manage gateway-owned values sealed on disk under /var/lib/coppice. secretNames on sandbox create and POST /sandboxes/:id/secrets inject selected values into /run/coppice/secrets.env without echoing plaintext in API responses. Receipt target: benchmarks/results/secrets-store/latest.txt. Tenant-scoped KMS/key rotation remains hosted-control-plane hardening. |
| Request-proxy credential injection | Cloudflare | Closed at the coppiceproxy layer. Operators start the proxy with -credential-env OPENAI_API_KEY, mint a short-lived token through /_coppice/credential-token, and hand the sandbox only /_coppice/credential-proxy/<token>/…. The proxy strips any sandbox-supplied Authorization header and injects the host-side credential when forwarding to the upstream. Receipt target: benchmarks/results/credential-proxy/latest.txt. Hosted KMS and tenant-scoped token issuance remain control-plane hardening. |
| Git convenience API | Cloudflare | Closed as a gateway convenience route. POST /sandboxes/:id/git/clone accepts repo, optional path, branch, depth, singleBranch, and recurseSubmodules, creates the parent directory, runs git clone inside the sandbox through the selected backend, and returns the resolved commit. Public repos work directly; private repo credentials compose with injected secrets, SSH deploy keys, or the host-side credential proxy. Receipt target: benchmarks/results/git-checkout-api/latest.txt. Progress events and SDK sugar remain ergonomics hardening, not a substrate gap. |
| Auto-archive to cold storage after N days idle | Daytona | ”Sandbox snapshot evicted to S3-class store after 7d, restorable on demand.” Closing this is a tiered eviction policy on the snapshots registry: ZFS send to a remote pool or to an object store, mark archived, restore on first reference. |
| Python decorator ergonomics + native object pass | Modal | Mostly closed as SDK-side ergonomics. examples/16-python-decorator.py ships a Modal-shaped @sandboxed helper: result = fn.remote(rows) creates or attaches to a Coppice sandbox, sends function source plus JSON-native arguments through /sandboxes/:id/exec, parses a JSON result marker, and tears down fresh sandboxes. Receipt: benchmarks/results/python-decorator/latest.txt. Arbitrary pickle/native-object transfer and a packaged Python SDK remain polish, not a gateway substrate gap. |
| CLI auth flow with OS-keyring | Sprites | Closed for the self-hosted CLI. coppice login accepts —token, —token-stdin, COPPICE_TOKEN, or E2B_API_KEY, verifies the token against /auth/whoami, then stores the token and gateway URL in the OS keyring when secret-tool or macOS security is available. Headless environments fall back to a 0600 credentials file, and COPPICE_CREDENTIALS_FILE makes receipts deterministic. coppice whoami and logout complete the flow. Receipt target: benchmarks/results/cli-auth/latest.txt. |
| Local IDE attach: VS Code Remote-SSH / JetBrains Gateway | Coder, Daytona | Closed for Linux/bhyve guests. Daytona documents token-based SSH access plus VS Code Remote-SSH and JetBrains Gateway. Coppice now exposes POST /sandboxes/:id/ssh-access for SSH-backed bhyve templates: it creates an expiring ed25519 key, injects the public key into the guest, returns a ~/.ssh/config stanza with ProxyJump honor, a one-line ssh command, and a VS Code Remote-SSH URI, then removes the key after TTL. Receipt target: benchmarks/results/ide-ssh-access/latest.txt. Browser code-server remains the FreeBSD-jail answer because Microsoft’s Remote-SSH server path is Linux/macOS/Windows-oriented rather than FreeBSD-jail-oriented. |
| Terraform provider | Coder | A first-class terraform apply path for sandboxes + templates. Mostly a matter of writing the provider; the API is already declarative-friendly. |
| Collaborative terminal with reconnect tokens | Cloudflare | Two browsers attached to the same shell tab, surviving WS drop. Today restty is single-attach; multiplexing it through a server-side PTY broker with reconnect tokens is the shape. |
| Shared template registry across teams | E2B | Templates today are per-host filesystem entries. A shared registry is two layers: a remote ZFS pool that hosts subscribe to (closed by cluster-overlay) plus a tenant model on top. |
Closed since capture
Rows from the original competitor pass are no longer gaps:
sandbox logs API, first-class tags,
readiness probes, async template build logs,
desktop screen recording, and
live resource resize. The post-capture pass also closed
the proxy-level signed preview URL primitive and the jail-backed
auto-suspend / AutoResume policy, plus the
lifecycle events read API.
Coppice now exposes
GET /sandboxes/:id/logs?limit=N&since=T and the v2
alias, backed by the per-sandbox ring buffer documented at
per-sandbox-logs. That is the
E2B-style “logs separate from metrics” surface, so it should not stay
in the open table. Tags are now accepted on create/fork, returned on
sandbox detail, listed via GET /tags, and filterable via
GET /v2/sandboxes?tag=<name> or
?tags=a,b. Readiness probes are accepted on create/fork as
readinessProbe / readiness_probe, can be baked
into a template via READINESS.json, and gate the response on
TCP or shell success. See readiness
probes. Template imports now also have a hosted-builder-shaped
surface: POST /templates/build returns a job id immediately,
GET /templates/builds/:job_id/logs polls retained output, and
GET /templates/builds/:job_id/events streams log/done events
over SSE. See template build jobs.
Desktop sandboxes now expose POST /sandboxes/:id/recordings
plus stop/status/list routes around ffmpeg -f x11grab on
the Xvnc display, with artifacts written under
/tmp/coppice-recordings. See
desktop-template.
Jail-backed sandboxes also expose
PATCH /sandboxes/:id/limits, which reapplies
rctl(8) CPU/memory caps and the sandbox dataset’s ZFS
quota without recreating the jail. See
live-limits; receipt:
mise run limits:live-resize-honor.
Auto-suspend is accepted on create with
lifecycle.onTimeout=“pause” and autoResume=true;
the reaper pauses idle jails and envd/files/commands traffic wakes them
again. See auto-suspend-resume;
receipt: mise run lifecycle:auto-suspend-resume-honor.
Lifecycle events now retain E2B-shaped
sandbox.lifecycle.created, paused,
resumed, updated, snapshotted, and
killed records at /events/sandboxes and
/events/sandboxes/:id. See
lifecycle-events; receipt:
mise run lifecycle:events-honor.
Lifecycle webhooks now add /events/webhooks, signed POST
delivery, retry attempts, and delivery-attempt history at
/events/webhooks/:id/deliveries; receipt:
mise run lifecycle:webhooks-honor.
Cube-specific demo rows are also closed and should not reappear as
market gaps: nginx BYOI is measured by
benchmarks/rigs/nginx-byoi-smoke.sh, OpenAI Agents
SDK is measured by the three agent-demo receipts, and
browser VS Code/code-server is measured by
examples/12-vscode.py plus the gateway’s
/vscode-proxy/:id/ route. Local IDE attach is also closed
for SSH-backed bhyve guests through /sandboxes/:id/ssh-access.
The CLI auth row is closed by coppice login,
whoami, and logout, with keyring-first storage
and a deterministic file-store receipt.
Next remaining quickest wins
The fastest path is not to tackle the hardest substrate rows first. The right order is to close the policy and UX rows that already sit on working primitives, while treating GPU passthrough as a measured feature with a separate CUDA-hardening backlog.
Closed in this pass: scheduled sandboxes, live volume hot-mounts, directory subtree snapshots, the first-class secrets store, request-proxy credential injection, and the first-class Git checkout route. The next shortest in-scope rows are:
- GPU CUDA kernel receipt. The passthrough and NVML
receipt is closed for the 2 GiB honor guest profile. Add a follow-up
rig that runs an actual CUDA kernel inside
debian-12-bhyve-gpu, captures toolkit versions, and records reset behavior across return/respawn. Outcome: GPU passthrough stays honest as a compute feature; GPU memory snapshots stay open. - Object-store-backed mounts. Bake an
rclone mountors3fstemplate path first, then decide whether this stays a mounted bucket feature or graduates into a Coppice-native cold storage tier. - Tenant management UX. API-key enforcement, coarse route scopes, create-path tenant quotas, and audit logs exist; the remaining work is durable org/key management and a hosted key-issuance surface.
What this list says about Coppice
Stripped of the hosted-only items: the gap between Coppice and the broader market is mostly policy and UX over primitives we already have. Auto-suspend, fork-tree visualization, signed preview URLs, scheduled sandboxes, live volume mounts, subtree snapshots, and managed secrets all followed that shape: two-to-five hundred lines of Rust on top of the substrate, not a substrate change. The two genuine substrate gaps are now tenant management UX (durable org/key state and hosted policy over the API-key primitive) and an object-store-backed storage tier (Sprites’ chunk store, Cloudflare’s R2 mounts, Daytona’s auto-archive). Managed secrets and the credential proxy cover both halves of the credential story: explicit injection when a process needs an env var, and host-side request brokering when it does not.
GPU passthrough is no longer a philosophical no or a roadmap-only
claim. Ordinary NVIDIA passthrough is measured through the Linux bhyve
template and ppt(4), with the current passing honor profile
kept at 2 GiB to avoid the high-address DMA fault seen at 4 GiB. GPU
memory snapshots remain a harder follow-up that needs device-state
measurements before we claim parity with Modal’s snapshot product.
Provenance
Captured 2026-04-24 from competitor public surfaces. Sources:
- Sprites — Fly.io. Code And Let Live, Design & Implementation, sprites.dev, Sprites Checkpoints API, Sprites CLI commands.
- E2B (hosted). E2B docs, AutoResume, Sandbox persistence, SSH access, Lifecycle events API, BYOC docs, Billing docs, Template build docs, e2b-dev/dashboard.
- Modal. Sandbox guide, Sandbox API, Sandboxes product page, pricing, GPU memory snapshots, Memory Snapshots docs.
- Cloudflare. Sandbox SDK overview, Preview URLs, Sandbox pricing, Containers + Sandboxes GA changelog.
- Daytona. Sandboxes docs, Declarative Builder, pricing, Fork & snapshot endpoints changelog.
- VS Code. Remote Development using SSH — the local IDE attach baseline: connect over SSH, install/run VS Code Server on the remote, run terminals/extensions/debugging there.
- Cube Sandbox. Architecture overview, dev-sidecar / connect existing cluster — proxy and wildcard-DNS routing shape, not a VS Code-specific local IDE feature.
- Other: Coder workspaces, Gitpod Flex, Northflank Workflows — sampled for the “in-scope” rows on Terraform, IDE handshakes, automation files.
Cross-cutting third-party comparison: Northflank: E2B vs Modal vs Fly.io Sprites.
Cross-refs
- /appendix/cubesandbox-feature-audit — the row-by-row Cube parity audit.
- /appendix/parity-gaps — the living tracker for Cube-shaped gaps.
- /appendix/cluster-overlay — cross-host primitive sketch (the substrate-layer follow-up implied by half the rows above).