Self-hosted dev sandboxes with preview URLs. One command. No Kubernetes, perfect for coding agents and Saas factories
# Add to your Claude Code skills
git clone https://github.com/tastyeffectco/sandboxesLast scanned: 6/5/2026
{
"issues": [],
"status": "PASSED",
"scannedAt": "2026-06-05T08:07:35.425Z",
"npmAuditRan": true,
"pipAuditRan": true
}No comments yet. Be the first to share your thoughts!
30 days in the Featured rail
Think of the apps where you type "build me a todo app" and seconds later a working website appears at its own link — like Lovable, Bolt, v0, or Replit. sandboxed is the open-source backend that makes that possible, running on your own server.
Here's what it does, in plain terms. You send it one HTTP request, and it:
POST /sandbox → a private, isolated container spins up
POST .../tasks → an AI agent writes an app inside it
http://<id>.preview... → that app is live at its own URL
It's also cheap to run: a sandbox goes to sleep when nobody's using it (freeing memory) and wakes up the instant someone opens its link again — files are saved on disk the whole time. So one ordinary server can hold many users instead of needing one virtual machine each.
Under the hood it's deliberately small and easy to understand: one Go program that tells Docker what to do, with Traefik handling the URLs and SQLite as the database. No Kubernetes, no separate database server, no message queue — you could read the whole thing in an afternoon.
┌──────────────── your host (just needs Docker) ────────────────┐
browser ──▶│ Traefik ──▶ sandbox (coding agent + dev server :3000) │
│ ▲ ▲ ▲ │
API/CLI ──▶│ sandboxd ─────────┘ └─ workspace dir (persists) │
│ │ SQLite (source of truth) · idle→stop · request→wake │
└─────┴────────────────────────────────────────────────────────-─┘
✅ Use it if you're running many sandboxes for other people — an AI app-builder ("describe an app → see it live"), an agent platform, a coding playground, per-user or per-branch preview environments, or multi-app hosting for a team.
❌ Skip it if you just need one or two containers for yourself — a shell
script, docker run, or lxd is simpler. (More on
that below.)
If you're building an AI app-builder, an agent platform, a coding playground, or a per-user preview product, the hard part isn't the prompt — it's the infrastructure underneath it:
That's months of platform work. sandboxed is that platform, distilled to one command:
./install.sh and you have a working API + previews.docker CLI + Traefik. A reconciler
converges Docker back to the database on every boot. You can read the whole
control plane in an afternoon.Fair question — and honestly: if you need one or two long-lived containers for
yourself, a shell script (or docker run, or lxd)
is simpler. Use that. We mean it. sandboxed is overkill for one-off projects.
It earns its keep the moment you're running many sandboxes for other
people — a team, or a product — because that's when the tidy little docker run script quietly grows into all of this:
opencode fired inline.Rebuild those as your script grows and you've rebuilt sandboxed. So: skip it for one-offs; reach for it when "just a script" has started keeping you up at night.
Prefer Kubernetes? The control plane talks to the container runtime through a thin
dockerCLI boundary, so a k8s Job/Pod backend is an interface swap, not a rewrite — a great first contribution. Today it targets a single Docker host (no k8s required), which is the sweet spot for teams who don't want to run a cluster just for sandboxes.
Requirements: Docker Engine + the Compose plugin, on Linux. That's it.
git clone https://github.com/tastyeffectco/sandboxes.git
cd sandboxes
./install.sh
install.sh checks Docker, writes a .env, builds the sandbox base image + the
control plane, and starts the stack. The API is then live at
http://127.0.0.1:9090 (verify: curl http://127.0.0.1:9090/healthz → ok).
The base image already includes the OpenCode and Claude Code CLIs. Hand
a sandbox a prompt and watch it build (OpenCode runs on its free plan out of the
box; pass your own provider key via env to use your account):
API=http://127.0.0.1:9090
# create a sandbox that will serve on port 3000
ID=$(curl -s -XPOST $API/sandbox -H 'content-type: application/json' \
-d '{"ports":[3000]}' | sed -E 's/.*"id":"([^"]+)".*/\1/')
echo "sandbox: $ID"
# spin a coding agent with a request — it works in ~/workspace/app
curl -s -XPOST $API/v1/sandboxes/$ID/tasks -H 'content-type: application/json' -d '{
"prompt":"create a Vite app that shows a todo list and run it on port 3000",
"agent":"opencode"
}'
# -> {"id":"<taskId>","status":"running","events_url":"/v1/sandboxes/<id>/tasks/<taskId>/events"}
# stream the agent's progress (Server-Sent Events)
curl -N $API/v1/sandboxes/$ID/tasks/<taskId>/events
To use your own model account instead of the free plan, inject a key at create time — it's available to the agent and any shell in the sandbox:
curl -s -XPOST $API/sandbox -d '{"ports":[3000],"env":{"ANTHROPIC_API_KEY":"sk-ant-..."}}'
Once the app serves on port 3000, it's reachable at its preview URL — the sandbox self-registered the route, nothing else to wire:
http://s-<id>-3000.preview.localhost
*.localhost resolves to 127.0.0.1 in every modern browser, so it works
locally with zero DNS and zero certificates (add :$HTTP_PORT if you changed it
from 80). The first request to a stopped sandbox wakes it automatically. On a
real domain you get https://s-<id>-3000.preview.yourdomain.com
(see Production / TLS).
Just want a shell, no agent? Skip step 2 and run anything via the exec API:
curl -XPOST $API/sandbox/$ID/exec -d '{"cmd":["bash","-lc","cd ~/workspace/app && python3 -m http.server 3000"]}'then open the same preview URL.
Base URL = http://127.0.0.1:9090 (set by SANDBOXED_API_BIND). Auth is off
by default for local use; with SANDBOXD_API_AUTH_DISABLED=false +
SANDBOXD_API_TOKENS, send -H "Authorization: Bearer <secret>".
| Method & path | Body | Purpose |
|---|---|---|
| POST /sandbox | {"ports":[3000],"env":{...}} | create — id optional (ULID auto); env injects vars (e.g. API keys) |
| GET /sandboxes | — | list all sandboxes |
| GET /sandbox/{id} | — | get one (status, ports, container id…) |
| POST /sandbox/{id}/exec | {"cmd":["bash","-lc","…"]} | run a command (non-interactive) |
| POST /sandbox/{id}/keepalive | — | postpone the idle reaper |
| POST /v1/sandboxes/{id}/stop | — | stop now to free RAM (wakes