Competitor gaps — what the broader market ships that Coppice doesn't

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 featureadvertised bywhy we don’t ship it
Cross-host / multi-region mobilitySprites, E2B, Modal, Daytona, CloudflareCoppice 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 sandboxesSprites, CloudflareRequires a fleet you don’t run. Coppice is the substrate for a fleet you do run.
Multi-host autoscale to 50 000+ sandboxesModalSingle-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 funnelall four hosted competitorsSelf-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 featureadvertised bywhat we have / what’s missing
Public preview URLs with per-port signed tokensCloudflare, Modal, SpritesClosed 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 visualizationDaytonaClosed. 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 tenantE2BClosed 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 backendE2BClosed 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)ModalFor 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 sandboxesModalOrdinary 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 featureadvertised bywhat closing it takes
Web dashboard — live stdout, kill, template managerE2B, Modal, Daytona, CloudflareClosed 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 permissionsE2BMostly 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 webhooksE2BClosed 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 sandboxE2B, 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 snapshotsModalDistinct 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 sandboxesModalClosed 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 sandboxModal (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)ModalClosed 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 storeModal, CloudflareClosed 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 injectionCloudflareClosed 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 APICloudflareClosed 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 idleDaytona”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 passModalMostly 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-keyringSpritesClosed 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 GatewayCoder, DaytonaClosed 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 providerCoderA 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 tokensCloudflareTwo 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 teamsE2BTemplates 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:

  1. 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.
  2. Object-store-backed mounts. Bake an rclone mount or s3fs template path first, then decide whether this stays a mounted bucket feature or graduates into a Coppice-native cold storage tier.
  3. 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:

Cross-cutting third-party comparison: Northflank: E2B vs Modal vs Fly.io Sprites.

Cross-refs