Sando is an experimental convenience service wrapper around Microsandbox. It packages common single-machine sandbox lifecycle operations behind a Bearer-authenticated control API, with a TypeScript SDK and a separate host-routed preview proxy.
The supported environments are Apple Silicon macOS for development and Linux with KVM for deployment. Install Bun before continuing.
packages/server— Bun service (@deablabs/sando)packages/sdk— dependency-free TypeScript client (@deablabs/sando-sdk)examples— runnable workflows built against the workspace SDKDESIGN.md— architecture, API contract, configuration, and security details
bun run build bundles both packages into their respective dist
directories.
Run the service directly from npm and install the SDK:
API_KEYS=dev-key DEFAULT_IMAGE=node:22 bunx @deablabs/sando
bun add @deablabs/sando-sdkbun install
API_KEYS=dev-key DEFAULT_IMAGE=node:22 bun startAPI_KEYS (a comma-separated list) and DEFAULT_IMAGE are required. The API
and preview listeners default to ports 4000 and 4001 and bind all interfaces.
Use http://127.0.0.1:4000 for local API access. The SDK reads
SANDBOX_API_KEY and optionally SANDBOX_API_URL, which defaults to that local
API URL.
Preview origins use PREVIEW_DOMAIN=preview.localhost and include port 4001 by
default. Sandbox responses expose one stable preview origin per requested guest
port. SDK reachability checks probe TCP connectivity; they do not determine
application health.
See the configuration table in DESIGN.md for listener, storage, resource,
network, exec, idempotency, and preview-auth settings. Common development
commands are:
bun run build
bun run test
bun run check
bun run fixSee PUBLISHING.md for the manual npm release checklist.
examples/opencode.ts starts a loopback-only Smart HTTP Git server, boots a
Node microVM, installs OpenCode, asks it to edit and push a branch, and starts
the resulting application as a durable exec. It then waits for TCP reachability
and checks the application's /health endpoint.
Put ANTHROPIC_API_KEY (or FIREWORKS_API_KEY) in the root .env, then run
the service and example in separate terminals:
HOST_CONNECTORS='{"local-git":{"port":7070,"protocol":"http","description":"OpenCode example Git fixture"}}' \
API_KEYS=dev-key DEFAULT_IMAGE=node:22 bun start
SANDBOX_API_KEY=dev-key bun examples/opencode.tsPass a custom task as an argument and use OPENCODE_MODEL to override the
provider-specific default model:
SANDBOX_API_KEY=dev-key bun examples/opencode.ts "Create a tiny Bun hello-world app"The host Git server remains on 127.0.0.1:7070; the guest reaches only the
registered connector at host.microsandbox.internal. Production clients would
normally use a remote Git service and appropriate credentials.
Other examples include compare.ts (forks and Vite previews) and the
phoenix-*.ts checkpoint, restore, and benchmark workflows. They require a
running manager and SANDBOX_API_KEY.
Trusted local development defaults to PREVIEW_AUTH=none. For protected
previews, set PREVIEW_AUTH=grant and mint a short-lived browser bootstrap URL:
const preview = sandbox.previews.get(3000);
await preview.waitUntilReachable({ timeoutMs: 30_000 });
const grant = await preview.createBrowserGrant({
expiresInSeconds: 300,
singleUse: true,
});
console.log(grant.url);Opening the URL exchanges the grant for a host-only HTTP-only cookie and
redirects to the clean preview origin. Grants and cookies are scoped to one
sandbox port. singleUse applies to grant redemption, not subsequent browser
requests.
Create, exec, fork, and checkpoint operations accept an optional idempotency
key. Reusing a key with the same API credential and input returns the original
response; reusing it with different input returns idempotency_key_reused.
Interrupted operations fail closed with idempotency_operation_interrupted.
See DESIGN.md for retention and retry semantics.
Exec sessions and output survive client disconnects, but not a manager restart.
Call stream.detach() before dropping an attachment, reattach with the saved
byte offset, and use kill() only when the remote process should terminate.
Checkpoint restore requires the API-key scope recorded on its source sandbox. Checkpoint names and checkpoint management are otherwise global to the manager. Checkpoints preserve disk and runtime configuration but not secret bindings.
Destination-scoped secret bindings pass a placeholder into the guest.
Microsandbox substitutes it in outbound request headers to exact allowed DNS
hostnames. Binding names must be valid, unique environment variable names and
must not collide with ordinary env entries. Wildcards, URLs, localhost, IP
addresses, and ports are not valid allowed hosts.
Secret bindings are not a credential vault or a general egress allowlist. Their plaintext exists transiently in client, service, and host runtime memory, and the service does not redact it from process output. Bound sandboxes cannot be forked, and restored checkpoints cannot receive bindings. Bindings also require reachable network policy: public egress or at least one host connector.
HOST_CONNECTORS registers fixed host-loopback TCP services. Clients request
connector names; the service resolves them to host.microsandbox.internal and
exposes the non-secret endpoint metadata in SANDBOX_HOST_CONNECTORS. Set
publicEgress: false for connector-only networking. Connectors provide network
authorization and discovery, not application credentials or TLS.
Expose the authenticated control API and wildcard preview listener separately,
prefer different registrable domains, and terminate TLS at a reverse proxy.
Set PREVIEW_SCHEME=https, set PREVIEW_DOMAIN to the preview suffix, and
leave PREVIEW_PUBLIC_PORT empty when public TLS uses port 443. Firewall both
listeners and the sandbox host-port range from direct public access.
This project is experimental and does not provide schema or API compatibility
with older versions. Recreate the configured DATA_DIR before opening state
written by an incompatible version. See DESIGN.md for the complete contract,
configuration defaults, non-goals, and security model.