{"slug": "self-hosted-dev-sandboxes-with-preview-urls-docker-go-no-k8s", "title": "Self-hosted dev sandboxes with preview URLs (Docker, Go, no K8s)", "summary": "The open-source project sandboxed provides a self-hosted platform that turns a single Linux machine into a fleet of isolated, on-demand development sandboxes with built-in AI coding agents and live preview URLs. The system, built with Go and Docker and requiring no Kubernetes, allows users to spin up containers via an HTTP API, automatically stops idle environments to conserve memory, and wakes them on the next request. This infrastructure enables developers to run AI app-builder products on their own hardware with one command, offering multi-tenant isolation, per-user preview URLs, and cost control without vendor lock-in.", "body_md": "**The open-source engine for AI app-builder products.**\n\nGive every user an isolated cloud dev environment, a built-in coding agent,\nand a live preview URL — self-hosted, on one machine, in one command.\n\n**sandboxed turns one Linux box into a fleet of isolated, on-demand dev\nsandboxes — each with a real shell, the common toolchains, a coding agent, and\nits own preview URL.** You drive it with a small HTTP API:\n\n```\nPOST /sandbox          → an isolated container spins up\nPOST .../tasks         → an AI agent builds an app inside it\nhttp://<id>.preview... → the running app, live, on a shareable URL\n```\n\nSandboxes **stop when idle** to free memory and **wake on the next request**, so\na modest server can host many of them. Workspaces persist on disk across stops\nand reboots. The whole thing is a single Go control plane that drives the Docker\ndaemon, fronted by Traefik — no Kubernetes, no database server, no message bus.\n\nThis 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.\n\n```\n            ┌──────────────── your host (just needs Docker) ────────────────┐\n browser ──▶│  Traefik  ──▶  sandbox  (coding agent + dev server :3000)      │\n            │     ▲              ▲   ▲                                        │\n API/CLI ──▶│  sandboxd ─────────┘   └─ workspace dir (persists)             │\n            │     │  SQLite (source of truth) · idle→stop · request→wake      │\n            └─────┴────────────────────────────────────────────────────────-─┘\n```\n\nIf you're building an **AI app-builder, an agent platform, a coding playground,\nor a per-user preview product**, the hard part isn't the prompt — it's the\ninfrastructure underneath it:\n\n**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.**\n\nThat's months of platform work. sandboxed is that platform, distilled to one command:\n\n- ⚡\n**One-command install.**`./install.sh`\n\nand you have a working API + previews. - 🧠\n**Agents included.** The OpenCode and Claude Code CLIs ship in every sandbox; hand a sandbox a prompt and it builds. - 💸\n**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. - 🔓\n**Yours.** Self-hosted, MIT-licensed, no vendor lock-in. Own your data, your margins, and your roadmap. - 🪶\n**Boring on purpose.** SQLite + the`docker`\n\nCLI + Traefik. A reconciler converges Docker back to the database on every boot. You can read the whole control plane in an afternoon.\n\nRequirements: **Docker Engine + the Compose plugin**, on Linux. That's it.\n\n```\ngit clone https://github.com/tastyeffectco/sandboxes.git\ncd sandboxes\n./install.sh\n```\n\n`install.sh`\n\nchecks Docker, writes a `.env`\n\n, builds the sandbox base image + the\ncontrol plane, and starts the stack. The API is then live at\n`http://127.0.0.1:9090`\n\n(verify: `curl http://127.0.0.1:9090/healthz`\n\n→ `ok`\n\n).\n\nThe base image already includes the **OpenCode** and **Claude Code** CLIs. Hand\na sandbox a prompt and watch it build (OpenCode runs on its free plan out of the\nbox; pass your own provider key via `env`\n\nto use your account):\n\n```\nAPI=http://127.0.0.1:9090\n\n# create a sandbox that will serve on port 3000\nID=$(curl -s -XPOST $API/sandbox -H 'content-type: application/json' \\\n       -d '{\"ports\":[3000]}' | sed -E 's/.*\"id\":\"([^\"]+)\".*/\\1/')\necho \"sandbox: $ID\"\n\n# spin a coding agent with a request — it works in ~/workspace/app\ncurl -s -XPOST $API/v1/sandboxes/$ID/tasks -H 'content-type: application/json' -d '{\n        \"prompt\":\"create a Vite app that shows a todo list and run it on port 3000\",\n        \"agent\":\"opencode\"\n     }'\n# -> {\"id\":\"<taskId>\",\"status\":\"running\",\"events_url\":\"/v1/sandboxes/<id>/tasks/<taskId>/events\"}\n\n# stream the agent's progress (Server-Sent Events)\ncurl -N $API/v1/sandboxes/$ID/tasks/<taskId>/events\n```\n\nTo 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:\n\n```\ncurl -s -XPOST $API/sandbox -d '{\"ports\":[3000],\"env\":{\"ANTHROPIC_API_KEY\":\"sk-ant-...\"}}'\n```\n\nOnce the app serves on port 3000, it's reachable at its preview URL — the sandbox self-registered the route, nothing else to wire:\n\n```\nhttp://s-<id>-3000.preview.localhost\n```\n\n`*.localhost`\n\nresolves to `127.0.0.1`\n\nin every modern browser, so it works\nlocally with zero DNS and zero certificates (add `:$HTTP_PORT`\n\nif you changed it\nfrom 80). The first request to a stopped sandbox **wakes it** automatically. On a\nreal domain you get `https://s-<id>-3000.preview.yourdomain.com`\n\n(see [Production / TLS](#production--tls)).\n\nJust 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\"]}'`\n\nthen open the same preview URL.\n\nBase URL = `http://127.0.0.1:9090`\n\n(set by `SANDBOXED_API_BIND`\n\n). Auth is **off\nby default** for local use; with `SANDBOXD_API_AUTH_DISABLED=false`\n\n+\n`SANDBOXD_API_TOKENS`\n\n, send `-H \"Authorization: Bearer <secret>\"`\n\n.\n\n| Method & path | Body | Purpose |\n|---|---|---|\n`POST /sandbox` |\n`{\"ports\":[3000],\"env\":{...}}` |\ncreate — `id` optional (ULID auto); `env` injects vars (e.g. API keys) |\n`GET /sandboxes` |\n— | list all sandboxes |\n`GET /sandbox/{id}` |\n— | get one (status, ports, container id…) |\n`POST /sandbox/{id}/exec` |\n`{\"cmd\":[\"bash\",\"-lc\",\"…\"]}` |\nrun a command (non-interactive) |\n`POST /sandbox/{id}/keepalive` |\n— | postpone the idle reaper |\n`POST /v1/sandboxes/{id}/stop` |\n— | stop now to free RAM (wakes on next preview hit) |\n`DELETE /sandbox/{id}` |\n— | destroy the container, keep the workspace |\n`POST /sandbox/{id}/purge` |\n— | destroy and delete the workspace |\n`POST /v1/sandboxes/{id}/tasks` |\n`{\"prompt\":\"…\",\"agent\":\"opencode\"}` |\nrun a coding agent headlessly |\n`GET /v1/sandboxes/{id}/tasks/{taskId}` |\n— | task result |\n`GET /v1/sandboxes/{id}/tasks/{taskId}/events` |\n— | live task event stream (SSE) |\n`GET/PUT /v1/sandboxes/{id}/files` |\n`{\"path\",\"content\",\"append\"}` |\nlist / read / write workspace files |\n`GET /healthz` , `GET /readyz` |\n— | liveness / readiness |\n\nA complete, copy-pasteable runbook (including driving it from your own agent) is\nin .\n\n`AGENTS.md`\n\n| Concern | Choice |\n|---|---|\n| Container runtime | Docker + hardened `runc` (cap-drop ALL, `no-new-privileges` , read-only rootfs) |\n| Workspace storage | one bind-mounted directory per sandbox under the data dir (persists) |\n| Edge / preview | Traefik v3 Docker provider — sandboxes self-register their routes |\n| Idle management | stop-on-idle (`docker stop` ) + wake-on-request; no warm pool |\n| State | SQLite (WAL); a reconciler converges Docker to the DB on boot |\n| Control plane | one Go binary, shells out to the `docker` CLI over the mounted socket |\n\nThe control plane runs in a container with the host Docker socket mounted and\nlaunches each sandbox as a sibling container on a shared network so Traefik can\nroute to it. Full design: [ ARCHITECTURE.md](/tastyeffectco/sandboxes/blob/main/ARCHITECTURE.md).\n\nEverything is in `.env`\n\n(created from [ .env.example](/tastyeffectco/sandboxes/blob/main/.env.example) on install).\nThe defaults run a complete local stack. The knobs you'll touch most:\n\n| Variable | Default | What it does |\n|---|---|---|\n`PREVIEW_DOMAIN` |\n`localhost` |\ndomain preview URLs hang off |\n`HTTP_PORT` |\n`80` |\nhost port Traefik listens on |\n`SANDBOXED_DATA_DIR` |\n`/var/lib/sandboxed` |\nwhere workspaces + state live |\n`SANDBOXED_API_BIND` |\n`127.0.0.1:9090` |\nwhere the control-plane API is published |\n`SANDBOXD_API_AUTH_DISABLED` |\n`true` |\nopen API for local use; set `false` + tokens for prod |\n\nFor a public deployment on a real wildcard domain:\n\n- Point\n`*.preview.yourdomain.com`\n\nat the host. - In\n`traefik/traefik.yml`\n\n, enable the`websecure`\n\nentrypoint 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\n`.env`\n\n:`PREVIEW_DOMAIN=yourdomain.com`\n\n,`PREVIEW_ENTRYPOINT=websecure`\n\n,`PREVIEW_TLS=true`\n\n, and**enable auth**—`SANDBOXD_API_AUTH_DISABLED=false`\n\nwith`SANDBOXD_API_TOKENS=name:secret`\n\n. `docker compose up -d`\n\n.\n\n```\n./uninstall.sh            # stop the stack + remove all sandboxes + network (keeps your data)\n./uninstall.sh --images   # also remove the built Docker images\n./uninstall.sh --data     # also DELETE all workspaces + state (asks to confirm)\n./uninstall.sh --all      # full removal: images + data\n```\n\nSafe by default — it removes only what sandboxed created (containers labelled\n`sandboxed.managed=true`\n\n, the compose stack, the network) and **keeps your\nworkspaces** unless you pass `--data`\n\n/`--all`\n\n.\n\nsandboxed 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:\n\n**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`\n\nceiling still applies; the gentler cgroup`memory.high`\n\nis opt-in.**Egress is default-allow, unlogged.** Add host firewall rules / a proxy for egress control.**Snapshots/templates are experimental** on directory storage.\n\nContributions toward any of these — and toward more agent backends — are very\nwelcome. See [ CONTRIBUTING.md](/tastyeffectco/sandboxes/blob/main/CONTRIBUTING.md).\n\nBe deliberate before exposing this to the open internet. Honest notes:\n\n**Sandboxes run real user code under hardened**(dropped capabilities,`runc`\n\n`no-new-privileges`\n\n, read-only rootfs, memory/PID/file-descriptor limits) — but this is**container 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`\n\n+`SANDBOXD_API_TOKENS`\n\n) 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.\n\nNone 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.\n\nYes — that's exactly the point. If you want to ship an **AI app-builder or agent\nSaaS** without first spending months building multi-tenant isolation, preview\nrouting, idle/wake cost control, and agent orchestration, sandboxed gives you\nthat core on day one, on a single inexpensive server, with margins you control.\nIt's a **strong, honest starting point** — beta-quality, MIT-licensed, and\ndesigned to be read and extended. Launch lean on it, harden the items above as\nyou grow, and contribute the improvements back.\n\n[MIT](/tastyeffectco/sandboxes/blob/main/LICENSE). Use it, ship it, sell what you build on it.", "url": "https://wpnews.pro/news/self-hosted-dev-sandboxes-with-preview-urls-docker-go-no-k8s", "canonical_source": "https://github.com/tastyeffectco/sandboxes", "published_at": "2026-06-03 19:43:37+00:00", "updated_at": "2026-06-03 19:49:46.358482+00:00", "lang": "en", "topics": ["ai-infrastructure", "ai-tools", "ai-products", "ai-agents", "mlops"], "entities": ["sandboxed", "Docker", "Go", "Traefik", "SQLite"], "alternates": {"html": "https://wpnews.pro/news/self-hosted-dev-sandboxes-with-preview-urls-docker-go-no-k8s", "markdown": "https://wpnews.pro/news/self-hosted-dev-sandboxes-with-preview-urls-docker-go-no-k8s.md", "text": "https://wpnews.pro/news/self-hosted-dev-sandboxes-with-preview-urls-docker-go-no-k8s.txt", "jsonld": "https://wpnews.pro/news/self-hosted-dev-sandboxes-with-preview-urls-docker-go-no-k8s.jsonld"}}