[ QUICKSTART ]
Quickstart: install → agent → preview
Requirements: Docker Engine + the Compose plugin, on Linux. That's it.
1. Install
git clone https://github.com/tastyeffectco/sandboxd.git
cd sandboxd
./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).
2. Have an agent build an app
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-..."}}'
3. Open the live preview
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).
:::caution Rancher Desktop / Docker Desktop + k3s
These bundle a k3s cluster whose klipper-lb grabs port 80 before sandboxd's
Traefik can, so preview URLs return 404. Fix: set HTTP_PORT=8080 in .env
and run docker compose up -d traefik. Preview URLs become
http://s-<id>-<port>.preview.localhost:8080.
:::
:::tip 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.
:::
Verify it's up
curl -s http://127.0.0.1:9090/healthz # -> ok
curl -s http://127.0.0.1:9090/readyz # -> ready
End-to-end example (no agent)
API=http://127.0.0.1:9090
# 1. create a sandbox exposing port 3000
ID=$(curl -s -XPOST $API/sandbox -H 'content-type: application/json' \
-d '{"ports":[3000]}' | sed -E 's/.*"id":"([^"]+)".*/\1/')
echo "sandbox=$ID"
# 2. start a dev server inside it
curl -s -XPOST $API/sandbox/$ID/exec -H 'content-type: application/json' \
-d '{"cmd":["bash","-lc","cd ~/workspace && echo hello > index.html && python3 -m http.server 3000"]}'
# 3. open the preview (browsers resolve *.localhost to 127.0.0.1).
# add :$HTTP_PORT if you changed it from 80.
curl -s -H "Host: s-$ID-3000.preview.localhost" http://127.0.0.1:${HTTP_PORT:-80}/
# 4. stop (idle) and wake-on-request
curl -s -XPOST $API/v1/sandboxes/$ID/stop
curl -s -H "Host: s-$ID-3000.preview.localhost" http://127.0.0.1:${HTTP_PORT:-80}/ # wakes it
# 5. destroy
curl -s -XPOST $API/sandbox/$ID/purge
Operate
docker compose logs -f sandboxd # control-plane logs
docker compose ps # stack status
docker compose restart sandboxd # restart control plane
docker ps --filter label=sandboxd.managed=true # list running sandboxes
Troubleshooting
readyznot "ready" / "docker info: exit status 1" — the control plane can't reach the Docker socket. Ensure/var/run/docker.sockis mounted (it is, in compose) and the daemon is running.- Port 80 already in use — set
HTTP_PORTin.env(e.g.8088) anddocker compose up -d. Preview URLs then need that port. id must be a ULID— you passed a non-ULIDid. Omitidto auto-generate.- Preview shows "Spinning up your app…" — the sandbox was stopped and is waking; it also shows if nothing is listening on the requested port yet.
- Seeding/permission errors on create — the daemon likely uses
userns-remap; keep the defaultSANDBOXD_USERNS=host.
Next
- Run coding agents headlessly → Agents
- Go public with HTTPS → Production / TLS
- Every endpoint → API reference