Skip to main content

[ QUICKSTART ]

Quickstart: install → agentpreview

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/healthzok).

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

  • readyz not "ready" / "docker info: exit status 1" — the control plane can't reach the Docker socket. Ensure /var/run/docker.sock is mounted (it is, in compose) and the daemon is running.
  • Port 80 already in use — set HTTP_PORT in .env (e.g. 8088) and docker compose up -d. Preview URLs then need that port.
  • id must be a ULID — you passed a non-ULID id. Omit id to 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 default SANDBOXD_USERNS=host.

Next