A self-hosted sandbox orchestration service that manages isolated Docker containers for LLM/VLM agents. Built on the architecture patterns of Anthropic's Claude Computer Use demo, extended with a warm-pool orchestration layer, multi-sandbox management, and real-time terminal streaming.
中文文档:README_CN.md
SandboxHub has two components that live in this monorepo:
| Component | Path | Role |
|---|---|---|
| Orchestrator | src/ |
Manages container lifecycle — warm pool, acquire/release, HTTP proxy |
| Ubuntu Image | images/ubuntu/ |
Ubuntu 22.04 sandbox — virtual desktop, FastAPI tool API, MCP server |
LLM Agent
│ POST /v1/sandboxes/acquire
▼
SandboxHub :8088 ─── warm pool ──→ Ubuntu Container
│ ├─ FastAPI :8000 (40+ REST tools)
│ proxy /v1/sandboxes/{id}/proxy/ ├─ FastMCP :8001 (30+ MCP tools)
▼ ├─ noVNC :6080 (web desktop)
response └─ VNC :5900
The Ubuntu sandbox image is directly inspired by Anthropic's computer-use-demo. Core design patterns carried over:
BashSessionPTY pattern — persistent bash subprocess with sentinel-based command completion detection (images/ubuntu/app/tools/bash.py)ToolResult/CLIResultabstractions — structured tool output for LLM consumption- Virtual desktop stack — TigerVNC + openbox + noVNC for VLM screenshot-and-click workflows
- Tool injection via lifespan —
BashTool,ComputerTool,EditToolsingletons injected into FastAPI routers at startup
SandboxHub adds on top:
- Warm pool — pre-warmed containers eliminate cold-start latency (<100ms acquire)
- Registry — tracks allocated containers per
(user_id, role_id)pair, enables reuse - HTTP proxy layer — single ingress point; routes all tool calls to the right container
- SSE streaming —
POST /api/terminal/execute/streamstreams stdout in real-time (extends the original polling model) - Multi-arch Dockerfile — builds on both amd64 (Google Chrome) and arm64 (Chromium)
docker build -t sandbox-ubuntu:latest images/ubuntu/Network note (China): The Dockerfile uses Aliyun APT mirrors, TUNA pip mirrors, and Gitee mirrors for noVNC/pyenv. No VPN needed for the build.
pip install -e .
cp .env.example .env # edit as neededKey .env settings:
DOCKER_IMAGE_UBUNTU=sandbox-ubuntu:latest
WARM_POOL_UBUNTU=3
SANDBOX_HUB_PORT=8088python main.pyOr with uvicorn directly:
uvicorn src.main:app --host 0.0.0.0 --port 8088 --reloadcurl http://localhost:8088/v1/health
# {"ok": true, "warm_pool": {"ubuntu": {"available": 3, "allocated": 0}}}curl -X POST http://localhost:8088/v1/sandboxes/acquire \
-H "Content-Type: application/json" \
-d '{"user_id": "u1", "role_id": "r1", "sandbox_type": "ubuntu"}'
# → {"sandbox_id": "sb_abc123", "status": "ready"}Returns in <100ms from the warm pool. Reuses an existing container if the same (user_id, role_id) pair already has one allocated.
curl -X POST http://localhost:8088/v1/sandboxes/sb_abc123/proxy/api/terminal/execute \
-H "Content-Type: application/json" \
-d '{"command": "ls /workspace", "timeout": 30}'
# → {"success": true, "output": "...", "error": null}curl -X POST http://localhost:8088/v1/sandboxes/sb_abc123/proxy/api/terminal/execute/stream \
-H "Content-Type: application/json" \
-d '{"command": "python train.py"}' \
--no-buffer
# data: {"type": "stdout", "chunk": "Epoch 1/10\n"}
# data: {"type": "stdout", "chunk": "loss: 0.42\n"}
# data: {"type": "done"}curl -X POST http://localhost:8088/v1/sandboxes/sb_abc123/proxy/api/screen/screenshot
# → {"image": "<base64-png>", "width": 1024, "height": 768}curl -X POST http://localhost:8088/v1/sandboxes/sb_abc123/release
# → {"ok": true}curl http://localhost:8088/v1/sandboxesThe Ubuntu container exposes 40+ REST endpoints and 30+ MCP tools. Key categories:
| Category | Endpoints | Description |
|---|---|---|
| Terminal | /api/terminal/execute, /execute/stream |
Bash commands, PTY session, SSE streaming |
| Screen | /api/screen/screenshot, /screenshot/region |
Full-screen or region capture |
| Mouse | /api/mouse/click, /move, /drag, /scroll |
Pixel-level mouse control |
| Keyboard | /api/keyboard/key, /type |
Key press, text input |
| File | /api/file/view, /create, /replace, /insert |
File read/write/edit |
| Browser | /api/browser/cdp/* |
Chrome DevTools Protocol — navigate, click, evaluate JS |
| System | /api/system/health, /clipboard, /info |
Health check, clipboard, system info |
| Process | /api/process/list, /kill |
Process management |
Full API docs available at http://localhost:8000/docs inside a running container.
| Variable | Default | Description |
|---|---|---|
SANDBOX_HUB_PORT |
8088 |
SandboxHub service port |
DOCKER_IMAGE_UBUNTU |
sandbox-ubuntu:latest |
Ubuntu sandbox image name |
WARM_POOL_UBUNTU |
3 |
Pre-warmed Ubuntu containers |
SANDBOX_NETWORK |
bridge |
Docker network mode |
SANDBOX_API_PORT |
8000 |
Port exposed by the container's FastAPI |
POOL_MAINTAIN_INTERVAL |
30 |
Seconds between pool replenishment checks |
SANDBOX_HTTP_PROXY |
(empty) | HTTP proxy injected into containers |
SandboxHub/
├── main.py # Entry point — python main.py
├── src/ # Orchestrator
│ ├── config.py
│ ├── main.py # FastAPI app
│ ├── manager/
│ │ ├── container_manager.py
│ │ ├── registry.py # (user_id, role_id) → container mapping
│ │ └── warm_pool.py # Pre-warmed container pool
│ ├── proxy/
│ │ └── forwarder.py # HTTP proxy to containers
│ └── routers/
│ ├── sandboxes.py # acquire / release / status
│ └── proxy.py # /v1/sandboxes/{id}/proxy/*
├── images/
│ └── ubuntu/
│ ├── Dockerfile # Multi-arch (amd64 + arm64)
│ ├── scripts/ # Container startup scripts
│ │ ├── entrypoint.sh
│ │ └── start_all.sh
│ └── app/ # FastAPI + MCP app (runs inside container)
│ ├── main.py
│ ├── mcp_server.py
│ ├── routers/ # 9 tool routers
│ └── tools/ # BashTool, ComputerTool, EditTool
├── tests/ # Orchestrator tests
└── images/ubuntu/tests/ # Sandbox app tests
- Add a new image directory:
images/<type>/Dockerfile - Register in
src/config.py:def image_for_type(self, sandbox_type: str) -> str: mapping = { "ubuntu": self.DOCKER_IMAGE_UBUNTU, "debian": self.DOCKER_IMAGE_DEBIAN, # new }
- Add
WARM_POOL_<TYPE>=Nto.env - No changes needed to Registry, Router, or Proxy
# Run orchestrator tests
pytest tests/ -v
# Run sandbox app tests
PYTHONPATH=images/ubuntu pytest images/ubuntu/tests/ -v
# Build image for a specific architecture
docker build --platform linux/amd64 -t sandbox-ubuntu:latest images/ubuntu/
# Run a sandbox container directly (without SandboxHub)
docker run -d --name sandbox --shm-size=2g \
-p 8000:8000 -p 8001:8001 -p 6080:6080 -p 5900:5900 \
sandbox-ubuntu:latestWarm pool pre-creates containers in the background so acquire returns in milliseconds. The pool maintainer runs every 30s to replenish containers consumed by allocations.
Graceful shutdown drains all containers (both warm pool and allocated) before exit, ensuring no orphaned Docker containers.
BashSession uses a persistent PTY with UUID-based sentinels to detect command completion. The streaming variant (run_stream) yields stdout line-by-line via asyncio.readline(), enabling real-time output for long-running commands.
VLM vs LLM routing: The sandbox supports both modalities. LLMs should use terminal/CDP endpoints (low token cost). VLMs can use screenshot + mouse/keyboard for pixel-level interaction.