Skip to content

Repository files navigation

Sandbox

E2B-compatible sandbox control plane. Use the standard E2B SDK with local Docker containers, Shuru microVMs, or Tart macOS VMs — swap to real E2B in production by changing one URL.

Backends

Docker Shuru Tart
Technology Linux containers macOS microVMs (Apple Virt) macOS VMs (Apple Virt)
Platform All macOS only macOS only
Guest OS Linux Alpine Linux macOS
Isolation Namespace/cgroup Hardware VM Hardware VM
Pause/resume Yes No (ephemeral) Yes (suspend/resume)
Status Default Supported Supported

See docs/container-runtimes.md for detailed backend comparison.

Quick Start

Docker (default)

# Build the base image
docker build -t sandbox-base:latest docker/sandbox

# Install
brew install circlesac/tap/sandbox
# or: npx @circlesac/sandbox

# Start (auto-generates API key on first run)
sandbox serve

Persistent Docker Desktop control plane

To run the control plane itself in Docker on macOS, enable Host networking under Docker Desktop's Settings → Resources → Network and enable Docker at login. Then start the sandbox service:

docker build -t sandbox-base:latest docker/sandbox
docker compose build --no-cache sandbox
docker compose up -d sandbox

The Compose service mounts the Docker socket and ~/.sandbox, listens on the host's port 49982, and restores itself after Docker Desktop or the Mac restarts. Use docker compose ps sandbox and sandbox status to verify both the container and the E2B control plane.

The service intentionally uses restart: always, not unless-stopped. Docker Desktop marks restored containers as stopped during a clean restart, and its host-network forwarder becomes ready after the Docker engine. The Compose startup command records the Docker VM boot ID, waits 12 seconds, and restarts the control plane once per VM boot so port 49982 is attached after host networking is ready. Keep that startup guard when changing the service.

Shuru (Linux microVMs on macOS)

brew install shuru
shuru checkpoint create sandbox-base --allow-net -- sh -c '...'

# Set backend
# ~/.sandbox/config.json → { "backend": "shuru" }
sandbox serve

Tart (macOS VMs)

# Install Tart and pull macOS base image (~24GB, one-time)
brew install cirruslabs/cli/tart
tart clone ghcr.io/cirruslabs/macos-sequoia-base:latest sandbox-base

# Build and install envd-lite into base image (one-time)
cd envd-lite
go generate ./...
go build -o envd-lite .
# See CONTRIBUTING.md for base image setup with envd-lite pre-installed

# Start with macOS support alongside Docker
SANDBOX_MACOS_BACKEND=tart sandbox serve

envd-lite is pre-installed in the base image with a LaunchAgent, so each sandbox only needs to clone the VM and boot it. VM creation takes ~30-60s depending on host CPU.

Public endpoint for a local control plane

When a remote Padawan, Worker, or test runner needs to reach a local control plane, run cloudflared in the same Docker Compose project. Derive a stable per-device hostname from the Mac's hardware serial so multiple machines do not claim the same DNS record:

SANDBOX_DEVICE_HASH="$(printf '%s' "$(ioreg -rd1 -c IOPlatformExpertDevice | awk -F'"' '/IOPlatformSerialNumber/{print $4}')" | shasum -a 256 | cut -c1-8)"
SANDBOX_TUNNEL_NAME="sandbox-${SANDBOX_DEVICE_HASH}"
SANDBOX_PUBLIC_HOSTNAME="sandbox-${SANDBOX_DEVICE_HASH}.crcl.es"

# One-time Cloudflare setup. `login` writes the local management certificate;
# the token file is the only credential mounted into the runtime container.
cloudflared tunnel login
cloudflared tunnel create "$SANDBOX_TUNNEL_NAME"
cloudflared tunnel route dns "$SANDBOX_TUNNEL_NAME" "$SANDBOX_PUBLIC_HOSTNAME"
cloudflared tunnel token "$SANDBOX_TUNNEL_NAME" > ~/.sandbox/cloudflared-tunnel-token
chmod 600 ~/.sandbox/cloudflared-tunnel-token

docker compose up -d cloudflared
docker compose ps sandbox cloudflared
curl -fsS "https://${SANDBOX_PUBLIC_HOSTNAME}/health"

The cloudflared container routes the tunnel to http://localhost:49982, uses host networking, waits for the sandbox health check, and has restart: always, so Docker Desktop restores both services after login or reboot. Never commit the token file or pass it on the command line.

Use https://sandbox-${SANDBOX_DEVICE_HASH}.crcl.es for both the E2B SDK's apiUrl and sandboxUrl. This is a physical-device naming convention for the control plane, not a Docker-only or per-container hostname.

Usage

The E2B SDK works the same across all backends. Use metadata.platform to select the platform:

import Sandbox from "e2b";

const opts = {
  apiUrl: "http://localhost:49982",
  apiKey: "sk-...",
  validateApiKey: false,
};

// Linux sandbox (default)
const linux = await Sandbox.create("base", opts);
const result = await linux.commands.run("uname");
// → "Linux"

// macOS sandbox
const macos = await Sandbox.create("base", {
  ...opts,
  metadata: { platform: "macos" },
});
const result2 = await macos.commands.run("sw_vers -productName");
// → "macOS"

Recent E2B SDKs validate official Cloud keys as e2b_<hex> before making a request. circlesac/sandbox uses its own authenticated sk-sandbox-* keys, so custom-control-plane clients must set validateApiKey: false. This disables only the SDK's local prefix check; the control plane still authenticates the key. For Padawan's PTY smoke, use the equivalent environment setting:

E2B_VALIDATE_API_KEY=false \
SANDBOX_API_URL=http://localhost:49982 \
SANDBOX_API_KEY="$(jq -r .apiKey ~/.sandbox/config.json)" \
bun run smoke:pty

Architecture

graph LR
    SDK[E2B SDK] --> CP[Control Plane :49982]
    CP --> |POST /sandboxes<br>DELETE /sandboxes/:id| Backend[Backend<br>Docker / Shuru / Tart]
    Backend --> |container/VM lifecycle| Sandbox[Sandbox]
    SDK --> |connectrpc| Proxy[Envd Proxy]
    Proxy --> |Process.Start<br>Filesystem.ListDir<br>/files, /health| Envd[envd<br>in sandbox]
Loading

See CONTRIBUTING.md for development setup and project structure.

About

Thin abstraction layer over sandbox providers (Docker, E2B, CodeSandbox, CF Sandbox) for Padawan compute

Resources

Contributing

Stars

3 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages