The open-source engine for AI app-builder products.
Give every user an isolated cloud dev environment, a built-in coding agent, and a live preview URL β self-hosted, on one machine, in one command.
sandboxed turns one Linux box into a fleet of isolated, on-demand dev sandboxes β each with a real shell, the common toolchains, a coding agent, and its own preview URL. You drive it with a small HTTP API:
POST /sandbox β an isolated container spins up
POST .../tasks β an AI agent builds an app inside it
http://<id>.preview... β the running app, live, on a shareable URL
Sandboxes stop when idle to free memory and wake on the next request, so a modest server can host many of them. Workspaces persist on disk across stops and reboots. The whole thing is a single Go control plane that drives the Docker daemon, fronted by Traefik β no Kubernetes, no database server, no message bus.
This is the infrastructure that sits behind "describe an app β watch it get built β see it running" products. sandboxed gives you that core, open source, on your own hardware.
βββββββββββββββββ 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 β
βββββββ΄ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ-ββ
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:
Multi-tenant isolation so one user's code can't touch another's.Per-user preview URLs with automatic routing and TLS.Cost controlβ idle environments must release memory, or your bill explodes.** Agent orchestration**β run a coding agent against a workspace, stream its progress, capture the result.** Persistence, wake-on-demand, reconciliation after a crash or reboot.**
That's months of platform work. sandboxed is that platform, distilled to one command:
- β‘
One-command install.
./install.sh
and you have a working API + previews. - π§
Agents included. The OpenCode and Claude Code CLIs ship in every sandbox; hand a sandbox a prompt and it builds. - πΈ
Dense by design. Stop-on-idle + wake-on-request means dozens of sandboxes share one box instead of one VM each β the difference between a $20 server and a $2,000 cluster. - π
Yours. Self-hosted, MIT-licensed, no vendor lock-in. Own your data, your margins, and your roadmap. - πͺΆ
Boring on purpose. SQLite + thedocker
CLI + Traefik. A reconciler converges Docker back to the database on every boot. You can read the whole control plane in an afternoon.
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
ID=$(curl -s -XPOST $API/sandbox -H 'content-type: application/json' \
-d '{"ports":[3000]}' | sed -E 's/.*"id":"([^"]+)".*/\1/')
echo "sandbox: $ID"
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"
}'
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 on next preview hit) | |
DELETE /sandbox/{id} |
||
| β | destroy the container, keep the workspace | |
POST /sandbox/{id}/purge |
||
| β | destroy and delete the workspace | |
POST /v1/sandboxes/{id}/tasks |
||
{"prompt":"β¦","agent":"opencode"} |
||
| run a coding agent headlessly | ||
GET /v1/sandboxes/{id}/tasks/{taskId} |
||
| β | task result | |
GET /v1/sandboxes/{id}/tasks/{taskId}/events |
||
| β | live task event stream (SSE) | |
GET/PUT /v1/sandboxes/{id}/files |
||
{"path","content","append"} |
||
| list / read / write workspace files | ||
GET /healthz , GET /readyz |
||
| β | liveness / readiness |
A complete, copy-pasteable runbook (including driving it from your own agent) is in .
AGENTS.md
| Concern | Choice |
|---|---|
| Container runtime | Docker + hardened runc (cap-drop ALL, no-new-privileges , read-only rootfs) |
| Workspace storage | one bind-mounted directory per sandbox under the data dir (persists) |
| Edge / preview | Traefik v3 Docker provider β sandboxes self-register their routes |
| Idle management | stop-on-idle (docker stop ) + wake-on-request; no warm pool |
| State | SQLite (WAL); a reconciler converges Docker to the DB on boot |
| Control plane | one Go binary, shells out to the docker CLI over the mounted socket |
The control plane runs in a container with the host Docker socket mounted and launches each sandbox as a sibling container on a shared network so Traefik can route to it. Full design: ARCHITECTURE.md.
Everything is in .env
(created from .env.example on install). The defaults run a complete local stack. The knobs you'll touch most:
| Variable | Default | What it does |
|---|---|---|
PREVIEW_DOMAIN |
||
localhost |
||
| domain preview URLs hang off | ||
HTTP_PORT |
||
80 |
||
| host port Traefik listens on | ||
SANDBOXED_DATA_DIR |
||
/var/lib/sandboxed |
||
| where workspaces + state live | ||
SANDBOXED_API_BIND |
||
127.0.0.1:9090 |
||
| where the control-plane API is published | ||
SANDBOXD_API_AUTH_DISABLED |
||
true |
||
open API for local use; set false + tokens for prod |
For a public deployment on a real wildcard domain:
- Point
*.preview.yourdomain.com
at the host. - In
traefik/traefik.yml
, enable thewebsecure
entrypoint and add a certificate resolver (Let's Encrypt DNS-01 is ideal β one wildcard cert covers every preview host, so you never hit per-host ACME limits). - In
.env
:PREVIEW_DOMAIN=yourdomain.com
,PREVIEW_ENTRYPOINT=websecure
,PREVIEW_TLS=true
, andenable authβSANDBOXD_API_AUTH_DISABLED=false
withSANDBOXD_API_TOKENS=name:secret
. docker compose up -d
.
./uninstall.sh # stop the stack + remove all sandboxes + network (keeps your data)
./uninstall.sh --images # also remove the built Docker images
./uninstall.sh --data # also DELETE all workspaces + state (asks to confirm)
./uninstall.sh --all # full removal: images + data
Safe by default β it removes only what sandboxed created (containers labelled
sandboxed.managed=true
, the compose stack, the network) and keeps your
workspaces unless you pass --data
/--all
.
sandboxed v1 optimizes for "runs anywhere with just Docker." A few things are deliberately simple β none affect the core loop (create β build β preview β idle β wake β persist), and each is a known place to harden:
No hard per-workspace disk quota. Workspaces are plain directories on a shared filesystem. Add fs/volume quotas if you need them.Soft memory throttle off by default. The hard per-sandbox--memory
ceiling still applies; the gentler cgroupmemory.high
is opt-in.Egress is default-allow, unlogged. Add host firewall rules / a proxy for egress control.Snapshots/templates are experimental on directory storage.
Contributions toward any of these β and toward more agent backends β are very welcome. See CONTRIBUTING.md.
Be deliberate before exposing this to the open internet. Honest notes:
Sandboxes run real user code under hardened(dropped capabilities,runc
no-new-privileges
, read-only rootfs, memory/PID/file-descriptor limits) β but this iscontainer isolation, not VM isolation. It's designed for** authenticated, accountable users running their own code**, not anonymous hostile multi-tenancy. If you need to run untrusted strangers' code, put each trust domain on its own VM, or add a stronger runtime (gVisor/Kata/Firecracker).The control plane holds the Docker socket, which is root-equivalent on the host. Treat the host as part of your trust boundary, keep it patched, and don't co-locate unrelated sensitive workloads.The API ships with auth disabled for a smooth local start.Enable it before any non-local deployment(SANDBOXD_API_AUTH_DISABLED=false
+SANDBOXD_API_TOKENS
) and never publish the API port to the internet unauthenticated.Egress is unrestricted by defaultβ a sandbox can reach the network freely. Add firewall/egress controls if that's a concern for your users.** Preview URLs are unauthenticated by default**(anyone with the URL can view a public sandbox). Private sandboxes gate access via a forward-auth hook; wire it up before serving sensitive previews.
None of this is exotic β it's the standard "you're running a server that executes code" checklist. Follow it and sandboxed is a solid base.
Yes β that's exactly the point. If you want to ship an AI app-builder or agent SaaS without first spending months building multi-tenant isolation, preview routing, idle/wake cost control, and agent orchestration, sandboxed gives you that core on day one, on a single inexpensive server, with margins you control. It's a strong, honest starting point β beta-quality, MIT-licensed, and designed to be read and extended. Launch lean on it, harden the items above as you grow, and contribute the improvements back.
MIT. Use it, ship it, sell what you build on it.