Provision isolated cloud computers for AI agents. Give any LLM a full Ubuntu desktop to use.
Sandbar spins up isolated Ubuntu 22.04 Docker containers — each with a full XFCE desktop, a pre-configured OpenClaw AI agent, and a REST API for computer control. Point an AI agent at it and it has a real computer to use.
Browser / External Agent / Claude Code MCP
│
▼
┌─────────────────────────────────────┐
│ Traefik v3 (HTTPS, Let's Encrypt) │
└─────┬─────┬──────┬─────┬───────────┘
│ │ │ │
/api /vnc /term /agent-api
│ │ │ │
┌─────▼─────▼──────▼─────▼───────────┐
│ Ubuntu 22.04 Container │
│ ┌─────────────────────────────┐ │
│ │ XFCE Desktop (noVNC) │ │
│ │ Chrome · VS Code · nvim │ │
│ │ OpenClaw AI Agent (TUI) │ │
│ │ Computer Control API │ │
│ └─────────────────────────────┘ │
└─────────────────────────────────────┘
| Component | Details |
|---|---|
| OS | Ubuntu 22.04 (isolated Docker container) |
| Desktop | XFCE4, accessible via browser (noVNC) |
| AI Agent | OpenClaw with Playwright browser control, session memory |
| Terminal | ttyd web terminal in the browser sidebar |
| Apps | Google Chrome, VS Code, LibreOffice, Neovim |
| Runtime | Python 3, Node.js 22, git, curl, wget |
| Control API | Screenshot, bash, click, type, key, scroll — all via REST |
- Multi-tenant platform — users get their own isolated computers, scoped by JWT auth
- Browser UI — tab bar with one tab per computer, VNC desktop pane + terminal pane
- REST API — create/delete computers, control them programmatically
- MCP server — use Sandbar computers as native Claude Code tools
- Discord integration — connect your AI agent to a Discord bot, no pairing codes
- Per-user Anthropic keys — each user's agent uses their own API key
- Docker + Docker Compose (Docker 24+)
- A Linux server with a public IP (for production HTTPS)
- A domain name pointed at your server
- An Anthropic API key
git clone https://github.com/jdrolls/sandbar.git
cd sandbar
cp .env.example .envEdit .env with your values:
ANTHROPIC_API_KEY=sk-ant-... # Your Anthropic key
SANDBAR_API_KEY=your-master-key # Generate: openssl rand -hex 32
JWT_SECRET=your-jwt-secret # Generate: openssl rand -hex 32
DOMAIN=sandbar.yourdomain.com # Your public domain
ACME_EMAIL=you@yourdomain.com # For Let's Encrypt
SANDBAR_BASE_URL=https://sandbar.yourdomain.comdocker build -t sandbar-base docker/base/This takes 5–10 minutes on first run (downloads Chrome, VS Code, OpenClaw).
docker compose up -dTraefik will automatically provision a Let's Encrypt TLS certificate for your domain.
# Wait a moment for the backend to initialize, then:
curl -X POST https://sandbar.yourdomain.com/api/admin/users \
-H "Authorization: Bearer $SANDBAR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"email": "admin@yourdomain.com",
"name": "Admin",
"password": "change-me-on-first-login",
"anthropic_api_key": "sk-ant-..."
}'Visit https://sandbar.yourdomain.com → log in → click + to create a computer.
The container takes ~30 seconds to boot. Once ready, the VNC desktop and OpenClaw terminal appear side by side.
For local dev without HTTPS:
# Use the dev docker-compose override
cp .env.example .env
# Set SANDBAR_BASE_URL=http://localhost and leave DOMAIN/ACME_EMAIL blank
docker build -t sandbar-base docker/base/
docker compose -f docker-compose.dev.yml up -dVisit http://localhost:9090. The backend API is at http://localhost:9090/api.
| Method | How | Used for |
|---|---|---|
| JWT cookie | POST /api/auth/login |
Browser sessions |
| Bearer token | Authorization: Bearer sbx_... |
Agents, MCP, scripts |
| Master key | Authorization: Bearer $SANDBAR_API_KEY |
Admin operations |
# Create a computer
curl -X POST https://sandbar.yourdomain.com/api/computers \
-H "Authorization: Bearer $YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"template": "sandbar-base"}'
# List your computers
curl https://sandbar.yourdomain.com/api/computers \
-H "Authorization: Bearer $YOUR_API_KEY"
# Delete a computer
curl -X DELETE https://sandbar.yourdomain.com/api/computers/{id} \
-H "Authorization: Bearer $YOUR_API_KEY"Each computer exposes a control API at /agent-api/{id}:
BASE=https://sandbar.yourdomain.com/agent-api/COMP_ID
AUTH="Authorization: Bearer $YOUR_API_KEY"
curl "$BASE/screenshot" -H "$AUTH" # Base64 PNG screenshot
curl -X POST "$BASE/bash" -H "$AUTH" \
-d '{"command": "ls /root"}' # Run shell command
curl -X POST "$BASE/click" -H "$AUTH" \
-d '{"x": 500, "y": 400}' # Click at coordinates
curl -X POST "$BASE/type" -H "$AUTH" \
-d '{"text": "hello world"}' # Type text
curl -X POST "$BASE/key" -H "$AUTH" \
-d '{"key": "Return"}' # Press keyFull API docs available at /api/docs (Swagger UI) after deployment.
# Create a user API key (login as that user first, or use cookie)
curl -X POST https://sandbar.yourdomain.com/api/auth/keys \
-H "Content-Type: application/json" \
-d '{"label": "my-agent"}'Use Sandbar computers as native tools in Claude Code:
cd mcp && npm installAdd to ~/.claude/settings.json:
{
"mcpServers": {
"sandbar": {
"command": "npx",
"args": ["-y", "sandbar-mcp"],
"env": {
"SANDBAR_BASE_URL": "https://sandbar.yourdomain.com",
"SANDBAR_API_KEY": "<your-api-key>"
}
}
}
}Available MCP tools: create_computer, list_computers, delete_computer, screenshot, bash, click, type_text, key_press, scroll, computer_info
Connect an AI agent to a Discord bot — responds to server messages and DMs:
- Create a bot at Discord Developer Portal
- Enable Message Content Intent under Bot → Privileged Gateway Intents
- Log into Sandbar → select a computer → click the chat icon in the terminal pane
- Paste your bot token → click Connect
The agent is live in ~30 seconds. No pairing codes required.
See docs/architecture.md for full technical details.
Key design decisions:
- No self-serve signup — admin creates users. Prevents resource abuse on self-hosted deployments.
- Containers run as root — required for Chrome/VS Code
--no-sandboxflags in Docker. Containers are isolated from each other and the host. - Docker socket access — the backend controls containers via the Docker socket (effectively host root for container operations). Treat the backend as a trusted process.
- SQLite — single-file database, sufficient for small-to-medium deployments. No external DB dependency.
- Raw IP routing — Traefik routes to container IPs directly (Docker DNS can be flaky). See docs/quirks.md.
See docs/deployment.md for production deployment instructions, backup setup, and upgrade procedures.
PRs welcome. Please:
- Fork the repo and create a feature branch
- Keep changes focused — one concern per PR
- Test with a real
docker compose upbefore submitting - Don't commit
.envor any secrets
MIT — see LICENSE.