Skip to main content

stackbox

One OpenSandbox-compatible endpoint for E2B, Modal, Daytona, Vercel, Cloudflare and your own OpenSandbox servers. Each sandbox goes to the cheapest or fastest provider that can run it, quotas are pooled across accounts, and failures fail over without duplicates or leaked sandboxes.

Clients don't change. The stock OpenSandbox SDKs (Python, Go, Kotlin, TS, C#), the osb CLI, and Harbor's opensandbox environment all talk to it as if it were one OpenSandbox server.

Quickstart

uv tool install "stackbox[all]"        # or: pip install "stackbox[all]"

export E2B_API_KEY=...                 # any provider; `stackbox providers` lists them all
stackbox doctor                        # checks each provider end to end, says what to fix
stackbox prepare ubuntu:24.04          # makes the image fast to start on every provider
stackbox serve                         # http://127.0.0.1:8090

No config file is needed:

  • providers are detected from their credentials;
  • prices and plan limits come from a built-in catalog;
  • the execd binary each sandbox runs is downloaded automatically.

stackbox init writes a stackbox.toml when you want to customise.

Python (pip install opensandbox; the snippet uses top-level await, as in Jupyter or python -m asyncio, and examples/quickstart.py is the same code as a script):

from opensandbox import Sandbox
from opensandbox.config import ConnectionConfig

gateway = ConnectionConfig(domain="http://127.0.0.1:8090", protocol="https")
sandbox = await Sandbox.create("ubuntu:24.04", connection_config=gateway)
print((await sandbox.commands.run("echo hello")).logs.stdout[0].text)
await sandbox.kill()

Harbor (examples/harbor.yaml): set the environment to opensandbox, domain to the gateway, and protocol: https. Then:

harbor run -c examples/harbor.yaml

Routing

Pick a default strategy with [routing] strategy or serve --strategy, and override it per request, OpenRouter-style:

Strategy Picks
balanced (default) the best mix of price and startup time
cost the lowest list price for what the provider would bill
latency the lowest learned time-to-sandbox for this image on this provider
priority backends in configured priority order

Per-request preferences go in headers (SDK: ConnectionConfig(headers=…)) or in sandbox metadata (Harbor: kwargs.metadata):

Header Metadata key Meaning
X-Stackbox-Strategy: cost stackbox.dev/strategy override the strategy
X-Stackbox-Providers: e2b,modal stackbox.dev/providers (e2b.modal) only these providers; without a strategy, try them in this order
X-Stackbox-Max-Price: 0.10 stackbox.dev/max-price skip providers above this USD per sandbox-hour
X-Stackbox-Backend: e2b stackbox.dev/backend pin to one provider, no fallback

Why a sandbox goes where it does: stackbox route [IMAGE] (or GET /v1/route?image=…) lists every provider in order with its price, expected startup time and status, for example:

  • next placement goes here;
  • full: 5/5 (learned from provider refusals);
  • not prepared; run stackbox prepare …;
  • too expensive.

Errors carry the same per-provider reasons and a hint.

How the routing behaves:

  • Learns startup time: the latency estimate is an average of observed creates per provider and image, weighted toward recent ones. It survives restarts, and each provider gets 3 measured starts before it can lose on its default estimate.
  • Discovers quotas: a provider's quota refusal (Daytona's memory cap, E2B's rate limit, Modal's resource limits, Cloudflare having no instances to hand out) is not a failure.
    • The gateway caps that provider at its current count, growing the cap back by one every 15 s.
    • New creates spill to other providers or wait.
    • Refusals don't count toward the circuit breaker.
  • Fails over only when it's safe:
    • A clean failure (nothing was created) moves on to the next provider.
    • An ambiguous one (timeout, 5xx) first checks the provider for the sandbox and adopts or deletes what it finds.
    • If the outcome can't be determined, it returns 503 STACKBOX::OUTCOME_UNKNOWN rather than risk a duplicate.
  • Breaker and retries: a per-backend circuit breaker demotes a backend after repeated failures, and retries go to other backends first.

CLI

Command What it does
stackbox providers supported providers, list prices, plan limits, which you have set up, and live status
stackbox init write stackbox.toml for the providers in your environment
stackbox doctor per provider: SDK, credentials, API reachable, execd, image prepared; each failure comes with its fix
stackbox prepare IMAGE… build templates and image layers, push to Vercel's registry; -p limits to some providers
stackbox serve run the server (--host, --port, --strategy)
stackbox route [IMAGE] where a sandbox would go right now, and why (--strategy, --providers, --max-price, --cpu, --memory)
stackbox images [IMAGE] prepared images per provider
stackbox sandboxes sandboxes the running server holds, with provider and cost (--all includes ended ones)
stackbox placement ID where one sandbox went, every attempt, and its cost
stackbox usage sandbox-hours, vCPU-hours, GiB-hours and estimated cost per provider (--since 24h)

stackbox providers also shows live status (held/limit, placements) when a server is running.

Every command takes -c FILE, and the read commands take --json. Config is found in this order:

  1. -c;
  2. $STACKBOX_CONFIG;
  3. ./stackbox.toml;
  4. auto-detection from credentials.

doctor exits 0 when everything passes, 1 when some providers have problems, and 2 when nothing is usable.

Providers

Kind Credentials Images Notes
e2b E2B_API_KEY any image; prepared as an E2B template with execd (about 45 s, once) Firecracker; a template fixes vCPU and memory
modal modal token set, or MODAL_TOKEN_ID + MODAL_TOKEN_SECRET any image; the execd layer is built once gVisor; 1 vCPU = half a Modal core
daytona DAYTONA_API_KEY any image, as a declarative image Daytona builds once and caches the account memory quota is discovered automatically
vercel VERCEL_TOKEN + VERCEL_TEAM_ID + VERCEL_PROJECT_ID Vercel's registry only; prepare pushes image + execd there (needs Docker once) bills active CPU only; Hobby caps sandboxes at 45 min
cloudflare CLOUDFLARE_BRIDGE_URL + CLOUDFLARE_BRIDGE_TOKEN fixed when the bridge is deployed deploy the bridge Worker once (bridges/cloudflare); bills active CPU only
opensandbox url (+ api_key) in config the server pulls them your own OpenSandbox servers on Docker or Kubernetes

On hosted providers the gateway emulates the OpenSandbox lifecycle on the provider's SDK and runs OpenSandbox's execd in every sandbox, with a per-sandbox token. GET /sandboxes/{id}/endpoints/44772 returns the provider's URL and the headers to send. Command and file traffic goes from the client to the sandbox, not through the gateway. Cloudflare is the exception: it goes through the bridge Worker.

Configuration

Every field is optional except kind; the provider catalog fills in the rest. See examples/all-providers.toml for every provider, and stackbox init for a starter.

[gateway]
api_key = "env:STACKBOX_API_KEY"   # required to listen beyond 127.0.0.1

[routing]
strategy = "balanced"

[[backends]]
kind = "e2b"
max_active = 50          # your plan's concurrency

[[backends]]
kind = "modal"
priority = 10
  • Secrets: "env:NAME" values (or "env:A|B") are read from the environment, so secrets stay out of config files.
  • Common backend fields:
    • max_active (concurrency);
    • priority;
    • cost_per_vcpu_hour and cost_per_gib_hour;
    • cpu_billed_fraction, for providers that bill only active CPU;
    • max_timeout_s;
    • default_cpu and default_memory_gib;
    • auto_prepare;
    • execd_path, if you'd rather supply execd yourself.

HTTP API

Hosted at https://stackbox.stacktrace.dev, or wherever you run stackbox serve. Every route except GET /health needs the API key in OPEN-SANDBOX-API-KEY.

OpenSandbox API (what the SDKs, Harbor and osb use, unchanged):

Route
POST/GET /v1/sandboxes, GET/DELETE /v1/sandboxes/{id} create, list, get, delete
/v1/sandboxes/{id}/endpoints/{port}, …/renew-expiration, … forwarded to the provider; hosted providers implement endpoints and renew-expiration and return 501 for the rest
/v1/snapshots, /v1/templates 501 STACKBOX::UNSUPPORTED; prepare images with POST /v1/images instead

stackbox API:

Route
GET /v1/providers, GET /v1/providers/{name} pricing, capacity (held, limit, learned quota), health, learned startup per image, prepared images, usage
GET /v1/route?image=…&strategy=…&providers=…&max_price=…&cpu=…&memory_gib=… where a sandbox would go right now, and why, for every provider (dry run)
GET /v1/placements/{sandbox_id} where a sandbox went, every attempt (outcome, error, duration), and its cost
GET /v1/images?image=…, POST /v1/images {image, providers?} prepared images per provider; prepare an image
GET /v1/usage?since=24h&until=…&group_by=provider|sandbox sandbox-hours, vCPU-hours, GiB-hours, estimated cost
POST /v1/admin/reconcile run cleanup now

Errors are JSON: {code: "STACKBOX::…", message, details, hint}. The message includes the hint, since SDKs show only the message. SANDBOX::NOT_FOUND keeps OpenSandbox's standard code.

Reliability

  • Ledger: a SQLite ledger records intent before every upstream create, and every attempt's outcome.
  • Reconciler: runs periodically and on POST /v1/admin/reconcile:
    • deletes orphans from ambiguous creates;
    • expires sandboxes that vanished upstream;
    • deletes leaked sandboxes carrying this gateway's managed tag.
  • Ownership: the tag holds a per-ledger instance id, so gateways sharing an account never touch each other's sandboxes.

Verified

All of these ran on real accounts with Harbor trials (an oracle agent, ubuntu:24.04, 1 vCPU, 2 GiB). Full write-ups: bench/LIVE.md and bench/LIVE-MORE.md; the local fault-injection benchmark is in bench/RESULTS.md.

  • Reliability: every gateway arm passed 48/48 with no leaked sandboxes. That covers E2B, Modal, Daytona, Vercel, Cloudflare, cost and latency routing, and quota pooling.
  • Strategy decides placement: cost chose E2B, 28% cheaper than Modal. latency chose the fastest-starting provider and followed it as providers sped up and slowed down.
  • Quotas:
    • Pooling two 4-sandbox quotas: 36 s, versus 69 s on one provider.
    • Daytona's 10 GiB account quota: 67–71 s through the gateway, versus 199 s with Harbor's own Daytona environment; 40–56 s with E2B taking the overflow.
  • Against Harbor's own provider environments:
    • E2B: 14% faster and 16% cheaper, traced to Harbor's E2B file transfers.
    • Vercel: 48/48 through the gateway, versus 10/48 native (a per-trial registry-token rate limit).
    • Cloudflare: Harbor has no environment of its own; the gateway is how it gets there.

Gateway create p50: Modal 0.6–0.8 s, Vercel 0.9 s, E2B 1.1–1.4 s, Cloudflare 1.5 s, Daytona 2.8–3.5 s.

Limits and next steps

  • Latency routing learns startup time, not task runtime. It picked Vercel for its fast starts, but Vercel trials ran longer. Next: learn per-provider runtime from completed sandboxes.
  • Hosted providers lack some OpenSandbox features: no egress sidecar (network policy), pause/resume or snapshots. Only E2B exposes ports other than execd's, and Modal can't extend a running sandbox.
  • Cost is an estimate from list prices and allocated resources, not invoices. Active-CPU billing is approximated with cpu_billed_fraction.
  • Cloudflare adds a hop: execd traffic goes through the bridge Worker, at about 165 ms per command versus about 110 ms elsewhere.
  • Listing doesn't scale yet: the list endpoint queries every backend on each call.
  • Later: a Kubernetes backend, region and GPU matching, and per-tenant quotas.

Development

git clone https://github.com/stacktrace-ai/stackbox && cd stackbox
uv sync --all-extras
uv run pytest -q                                  # all tests run against in-memory fakes
uv run ruff check src tests bench scripts examples && uv run ruff format

Contributor guidelines and the developer-experience principles are in CLAUDE.md. Releases ship by pushing a v<version> tag; see docs/releases/ and the release-stackbox skill.

Metadata

Release files for stackbox 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for stackbox 0.1.0
File Size Uploaded Provenance Actions
stackbox-0.1.0.tar.gz 60.9 kB With provenance Details for stackbox-0.1.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for stackbox 0.1.0
File Interpreter ABI Platform Actions
stackbox-0.1.0-py3-none-any.whl Python 3 none any Details for stackbox-0.1.0-py3-none-any.whl

Total release size: 133.9 kB

Release files / stackbox-0.1.0.tar.gz

Download URL stackbox-0.1.0.tar.gz
Size 60.9 kB
Tags Source
SHA-256 checksum
How to use checksums
f6411a796da35c64e441a64d76073b63c678b85b593455fb1bcbecce77ef0b09
BLAKE2b-256 checksum
How to use checksums
6b05cb55ac3abe51ee320d6036931f3f4bc124ae374e857aebffd227364d7b2e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 9, 2026.

Transparency log

Release files / stackbox-0.1.0-py3-none-any.whl

Download URL stackbox-0.1.0-py3-none-any.whl
Size 73.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
d0e39afae2bedaece844327cfab93732b4c86d66416c377801df98c8a9de8948
BLAKE2b-256 checksum
How to use checksums
a104119e3198f7e281f4148e23faf51fbda73eb4383bcb8477a159b6e2aa1d2d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 9, 2026.

Transparency log

Security for stackbox 0.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

Both files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for stackbox-0.1.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 9, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page