SSD Nodes Learn 🎉 VPS $4.99/月起
指南 Matt Connor作者: Matt Connor

在 VPS 上自行托管 sandboxd AI 应用构建器

了解如何在自有 VPS 部署 sandboxd:固定版本安装、配置模型密钥、启用 HTTPS 预览 URL,并核对内存、磁盘下限与过期 sandbox 清理方法。

sandboxd 是什么,以及自行运行它能获得什么

要自行托管 sandboxd,您需要一台安装了 Docker 的 Linux 服务器和一个域名。您发送提示词后,代码代理会在隔离容器中构建一个真实应用,该应用随后会通过专属预览 URL 提供访问。提示词生成应用是 2026 年最受关注的托管服务类别,而 sandboxd 可运行在您自己的 VPS 上,采用 MIT 许可证,生成的代码也保存在您自己的磁盘中。

该设计有意保持简单。Go 控制平面驱动 Docker,Traefik v3 为每个预览主机名提供路由,SQLite 保存状态,每个应用运行在一个容器中。不使用 Kubernetes,也不需要独立的数据库服务器,因此一台配备 2 vCPU 的服务器也能运行它。

整个模型由 4 个对象组成。app 是持久化项目,保存名称、git 元数据和密钥。sandbox 是运行该 app 的 Docker 容器,一个 app 同一时间只关联一个 sandbox。workspace 是 app 的文件,存储在主机上,即使容器被删除也会保留。task 是交给 sandbox 内代理执行的一条提示词。停止 sandbox 会释放内存,但文件仍会保留。销毁 sandbox 会删除容器,之后 app 可以启动一个新的 sandbox。

sandboxd 与 Dify 和 OpenHands 有何不同?

之所以容易混淆这三个项目,是因为它们都会在您的服务器上运行 LLM(大语言模型),但生成的内容不同。Dify 构建 LLM 应用:聊天界面、检索流程,以及每次有人使用时都会调用模型的工作流。模型是最终产品的一部分。OpenHands 处理您已有的代码仓库:您将代码指向它,它会读取文件、运行命令并提出修改建议。sandboxd 则从零开始。它根据预设创建项目脚手架,在全新的容器中构建项目,并提供一个供您查看的 URL。生成的是普通的 React 或 FastAPI 应用,运行时不需要模型。

因此,应根据最终目标进行选择。如果您想从一句描述开始,并在之后保留代码,请使用 sandboxd。如果代码仓库或依赖模型的产品已经存在,请使用另外两个项目。

另一个区别是项目年龄。在基于它构建实际项目之前,您需要重点评估这一点。

ChartGitHub stars and forks, read from the GitHub API on 4 August 2026
The data behind this chart
[
  {
    "tool": "sandboxd",
    "github_stars": "875",
    "forks": "50"
  },
  {
    "tool": "OpenHands",
    "github_stars": "83,091",
    "forks": "10,711"
  },
  {
    "tool": "Dify",
    "github_stars": "151,320",
    "forks": "23,886"
  }
]

sandboxd 目前有 875 个 stars,OpenHands 有 83,091 个,Dify 有 151,320 个。该仓库创建于 2026 年 6 月 3 日,因此截至 2026 年 8 月已有两个月;OpenHands 始于 2024 年 3 月,Dify 始于 2023 年 4 月。版本 v0.1.0 于 2026 年 6 月 6 日发布,v0.3.6 于 2026 年 8 月 1 日发布。该项目将自身称为 beta,并说明 0.x 版本可能破坏兼容性。应将这些数字视为依赖风险,而不是对项目质量的结论:一个只有两个月历史的项目,也只经历了两个月的他人漏洞发现过程。

服务器所需资源,以及资源不足时会出现的问题

项目说明,启动时需要 2 个 vCPU 和 4 GB 内存。这对于控制平面加一个小型沙箱是准确的,但不足以支持两个人同时构建。请分项规划内存。Traefik 和 Go 控制平面占用很少。每个运行中的沙箱都包含完整的 Node 或 Python 工具链,峰值出现在 npm install,随后还要执行生产构建。对于需要持续运行几个应用的主机,建议规划 8 GB 内存。应将 swap 视为安全缓冲,而不是可用容量,因为发生 swap 的构建需要几分钟,而不是几秒。

内存耗尽时会出现两种不同的故障,表现完全不同。在沙箱内,容器会达到 sandboxd 设置的硬 --memory 上限,内核随后终止占用内存最多的进程,因此构建会失败,agent 不会提供有用的错误信息。docker ps -a 会显示该容器的退出码为 137,针对该容器运行 docker inspect 会报告 "OOMKilled": true。以这种方式失败的 Node 构建通常会先输出 JavaScript heap out of memory

第二种故障发生在主机上。sandboxd 运行压力回收器,在主机内存不足时停止沙箱。因此,在小型主机上观看预览时,沙箱可能会消失。文件不会丢失;下一次请求预览 URL 时会将其唤醒。但容器停止时正在运行的任务不会恢复。

磁盘空间是较不明显的问题。每个应用都在主机上保留自己的工作区,而 JavaScript 项目会包含一个数百 MB 的 node_modules 目录。10 个应用在计算镜像之前,就可能需要数 GB 的依赖项空间。请从 40 GB 开始,并持续监控:

docker system df
sudo du -sh /var/lib/sandboxed/workspaces

默认数据目录为 /var/lib/sandboxed,其中包含额外的 e。输入 /var/lib/sandboxd 会得到一个空目录,并因此浪费五分钟排查问题。

安装固定版本的 sandboxd

必须先在服务器上安装带 Compose plugin 的 Docker Engine,以及 git。在 VPS 上安装 Docker介绍了这部分内容。

docker compose version
git --version

两条命令都必须输出版本号。docker: 'compose' is not a docker command表示系统中有旧版独立 docker-compose二进制文件,而安装程序需要 v2 plugin。

安装程序是通过网络获取的 shell 脚本。因此,运行前应先阅读脚本,并固定版本。

curl -fsSL https://raw.githubusercontent.com/tastyeffectco/sandboxd/v0.3.6/install.sh -o install-sandboxd.sh
less install-sandboxd.sh
SANDBOXD_REF=v0.3.6 bash install-sandboxd.sh

SANDBOXD_REF是安装程序检出到 $HOME/.sandboxd/src中的 git ref,默认值为 main。如果不设置该值,安装的就是当天早上合并的内容。对于仅在 2026 年 7 月就发布了 6 个版本的项目,这一点很重要。请固定版本,并在阅读变更日志后再有计划地升级。

脚本会克隆源代码、构建镜像,并使用 docker compose up -d启动整个服务栈。最后会输出控制台 URL 和 API token。请将该 token 安全保存。它是一个可通过 Docker 执行 root 操作的 API 的凭据。

curl http://127.0.0.1:9090/healthz

控制平面启动后,该命令会输出 ok。如果没有任何输出,说明服务栈未启动:运行 docker compose ps(工作目录为 ~/.sandboxd/src)查看哪个服务已停止,然后运行 docker compose logs sandboxd查看原因。

访问远程主机上的控制台

控制台通过 Traefik 提供服务,Traefik 监听 HTTP_PORT,默认端口为 80,访问主机名为 http://console.localhost。Traefik 根据主机名进行路由,因此在浏览器中输入服务器的 IP 地址不会匹配任何规则,并返回 404。在设置真实域名之前,请转发该端口并保留主机名:

ssh -L 8080:127.0.0.1:80 you@your-vps

然后在笔记本电脑上打开 http://console.localhost:8080。在 Linux 和 macOS 中,任何以 .localhost 结尾的名称都会解析为 127.0.0.1,因此请求会通过隧道发送,并携带正确的 Host 请求头。首次访问时设置控制台密码。

为代理指定模型

基础镜像中包含两个编码代理:OpenCode 和 Claude Code。SANDBOXD_DEFAULT_AGENT 决定未指定代理的任务使用哪一个,默认使用 opencode。完全未连接密钥时,任务会使用 OpenCode Zen 提供的免密免费模型,因此首次构建无需付费,您可以在产生费用前测试完整流程。

需要更强的模型时,请连接您自己的密钥。密钥会发送到控制平面,不会进入沙箱:密钥以加密形式存储在数据目录中,并由凭据代理在网络传输时注入。因此,代理和它生成的代码都无法读取密钥。

export API=http://127.0.0.1:9090
export SANDBOXD_TOKEN=sk_...                       # printed by the installer
export AUTH="Authorization: Bearer $SANDBOXD_TOKEN"

curl -s -XPOST $API/v1/agents/claude-code/api-key -H "$AUTH" \
  -H 'content-type: application/json' \
  -d '{"api_key":"sk-ant-..."}'

控制台也可在 Settings、AI Agents 中完成相同配置。如果您希望使用 Claude 订阅而不是 API key,其中还提供引导式 OAuth 流程。每个代理的默认模型也在同一面板中设置,单个任务可以覆盖该设置。

端到端构建一个小型应用

创建应用,启动其沙箱,然后发送提示。ID 会以 JSON 返回,快速入门示例使用 sed 提取这些 ID,因此无需安装 jq

APP=$(curl -s -XPOST $API/v1/apps -H "$AUTH" \
  -H 'content-type: application/json' \
  -d '{"name":"todo","runtime_preset":"react-vite"}' \
  | sed -E 's/.*"id":"([^"]+)".*/\1/')

SB=$(curl -s -XPOST $API/v1/apps/$APP/sandbox -H "$AUTH" \
  -H 'content-type: application/json' -d '{"ports":[3000]}' \
  | sed -E 's/.*"id":"([^"]+)".*/\1/')

echo "app=$APP sandbox=$SB"

两个变量都必须包含 ID。空的 $SB 表示沙箱从未启动,通常是因为基础镜像仍在构建,或主机内存不足。若返回 401 而不是 ID,表示 bearer token 错误。

curl -s -XPOST $API/v1/sandboxes/$SB/tasks -H "$AUTH" \
  -H 'content-type: application/json' \
  -d '{"prompt":"Add a todo list with a text input, an add button, and a delete button on each row. Keep the list in localStorage.","agent":"opencode"}'

响应中包含任务 ID。GET /v1/sandboxes/$SB/tasks/<task id> 返回任务结果,同一任务的 /events 路径提供实时 SSE(服务器发送事件)流,显示 agent 正在执行的操作。控制台会以聊天形式显示相同的流。

应用随后位于 http://s-<sandbox id>-3000.preview.localhost,其中 3000 是您请求使用的端口。如果沙箱处于休眠状态,第一个请求会到达 Traefik 的 catch-all 路由;sandboxd 启动容器,等待该端口响应,然后提供一个短暂的预热页面,并刷新到您的应用。如果预览始终停留在该页面,表示进程没有监听应用的 sandbox.yaml 中声明的端口。

使用正式域名和 HTTPS 提供预览环境

每个沙箱都有自己的主机名,因此一条通配符 DNS 记录即可覆盖所有沙箱。为 *.preview.yourdomain.com 创建 A 记录,将其指向服务器的 IP 地址。然后在 ~/.sandboxd/src 中设置 .env 的预览变量:

PREVIEW_DOMAIN=yourdomain.com
PREVIEW_ENTRYPOINT=websecure
PREVIEW_TLS=true
SANDBOXD_API_AUTH_DISABLED=false

Traefik 还需要相应配置:在 traefik/traefik.yml 中启用 websecure 入口点,并添加证书解析器。使用 DNS-01 挑战,因为一张通配符证书即可覆盖所有预览主机名。使用 HTTP-01 时,每个新沙箱都需要单独申请证书。集中构建可能很快触发 Let's Encrypt 的速率限制。通过 DNS-01 挑战申请通配符证书介绍了 DNS 相关配置。

cd ~/.sandboxd/src
docker compose up -d

预览 URL 将变为 https://s-<id>-3000.preview.yourdomain.com。在防火墙上放行 80 和 443,禁止公网访问 9090:参见基本 ufw 防火墙规则。请注意,任何能猜到预览主机名的人都可以加载应用,因此应将预览环境视为公开环境。

生成的代码保存在哪里?可以导出吗?

代码保存在主机的数据目录中。每个工作区都是 /var/lib/sandboxed/workspaces/<id>/ 下的普通目录,并绑定挂载到容器中;应用文件位于沙箱内的 /home/sandbox/workspace/app。控制平面状态保存在单个 SQLite 文件 state/sandboxd.db 中,加密的代理凭据保存在 agent-auth/ 中。没有任何数据隐藏在容器层中,因此备份只需复制目录和该数据库文件。VPS 上的 restic 备份可以同时处理这两项。

sudo ls /var/lib/sandboxed/workspaces
sudo du -sh /var/lib/sandboxed/workspaces/*

系统内置 Git 导出功能,无需额外集成。API 提供状态和差异读取接口,也支持提交和推送:

curl -s $API/v1/apps/$APP/git/status -H "$AUTH"

curl -s -XPOST $API/v1/apps/$APP/git/commit -H "$AUTH" \
  -H 'content-type: application/json' \
  -d '{"message":"todo list, first pass"}'

curl -s -XPOST $API/v1/apps/$APP/git/push -H "$AUTH" \
  -H 'content-type: application/json' -d '{"branch":"main"}'

私有远程仓库需要个人访问令牌。在控制台的 Settings、Git credentials 中设置一次即可。令牌会加密存储,并保存在沙箱之外,因此代理无法读取它,也不能在您不知情的情况下使用它推送代码。请尽早并经常推送。在执行推送之前,工作区目录是代码的唯一副本,而 DELETE /v1/apps/<id> 会直接删除它,且无法恢复。

构建会消耗多少模型 token?

sandboxd 不会统计您的费用,因此应以提供商控制台中的数据为准。免费的 OpenCode Zen 模型不收费,但速度和能力都低于付费模型。对于超出简单示例应用范围的任务,这通常会表现为需要更多轮修正。

费用取决于代理循环的工作方式。每一轮都会重新发送所需的上下文,因此费用取决于轮数,而不是应用数量。一次就能得到正确结果的提示词,成本很低。对于包含 50 个文件的项目,连续 15 轮要求“现在修复间距”则不同,因为每次都会重新携带文件内容。输入和输出 token 的计费方式不同编码代理每次会话的费用介绍了更符合实际的费用范围。在让无人值守运行的循环开始执行前,请先在提供商处设置严格的费用上限。

清理过期沙盒

空闲回收器会停止闲置时间超过 SANDBOXD_IDLE_THRESHOLD_SECONDS 的沙盒。默认值为 2100 秒,即 35 分钟。此操作会释放 RAM,但会保留文件;下次访问预览 URL 时,容器会被唤醒。在配置较小的主机上应缩短该时间,因为容器空闲 35 分钟,就意味着有 35 分钟的内存无法使用。

停止不等于删除,磁盘空间通常会在这里悄悄耗尽。已停止的沙盒仍会占用其工作区和容器。删除沙盒但保留应用,是对沙盒执行 DELETE,会同时删除容器和工作区。删除应用则会永久删除所有内容。

curl -s -XPOST $API/v1/sandboxes/$SB/stop -H "$AUTH"     # frees RAM, keeps files
curl -s -XDELETE $API/v1/sandboxes/$SB -H "$AUTH"        # container and workspace gone
curl -s -XDELETE $API/v1/apps/$APP -H "$AUTH"            # app and everything under it

经过几周的实验后,docker system df 显示的可回收镜像空间可能比预期更多,因为每个自行拉取工具链的应用都会留下镜像层。docker image prune 会清理悬空镜像。先检查 GET /v1/apps,因为仍被休眠沙盒引用的镜像不属于垃圾数据。

容器边界能提供和不能提供什么

每个沙箱都以非特权用户运行,使用只读根文件系统,删除所有 Linux capability,设置 no-new-privileges、内存上限和进程数限制。该项目明确承认其边界:共享内核的 Linux 容器是较强的隔离边界,但不是强安全边界。内核漏洞可能导致主机遭到入侵。

有两点需要采取措施。自托管构建中的沙箱允许网络出站,因此生成的代码可以访问互联网、本地网络和云元数据端点。源码中存在 nftables 出站子系统,但便携式 Docker Compose 构建会将其编译为关闭状态,这意味着限制必须由主机防火墙提供。控制平面 API 实际上等同于 host root,因为它驱动 Docker socket。它默认绑定到 127.0.0.1:9090SANDBOXD_API_AUTH_DISABLED 必须保持为 false,并且绝不能发布到互联网。

如果您计划让其他人向您的主机发送提示词,单独采用这种模型不够安全。该项目建议使用带有 SANDBOXD_RUNTIME=runsc 的 gVisor,在沙箱与主机之间加入用户态内核,但系统调用密集型工作大约会慢 1.7 到 4 倍。更强的方案是为每个租户使用一台独立机器,这与在一次性 VM 中运行编码代理的理由相同。

是否应该基于一个存在两个月的项目构建?

对于个人构建服务器,可以,但必须采取基本的预防措施:固定 SANDBOXD_REF 的版本,备份 /var/lib/sandboxed,并将所有重要应用推送到 git 远程仓库。对于客户会接触到的任何内容,应等到 1.0,或预留应对不稳定变更的预算,因为维护者明确表示,0.x 版本可能在使用期间发生不兼容变更。截至 2026 年 8 月,维护者还以每月 79 美元的价格提供托管安装服务。在判断该项目是否有持续存在的理由时,这一点也值得了解。

之所以可以接受这种风险,是因为它的输出结果可靠。sandboxd 会在普通的 git 仓库中生成普通应用,因此即使项目停止维护,您仍会保留代码,失去的只有包装层。这比使用一个由托管构建平台控制项目的方案更有保障。若要进一步了解今年哪些项目值得放在自己的服务器上,请参阅 2026 年哪些项目值得自行托管

FAQ

sandboxd 的最低服务器配置是什么?

项目说明,2 个 vCPU 和 4 GB RAM 足以启动服务,可运行控制平面、Traefik 和一个小型 sandbox。如果要同时运行多个应用,请使用 8 GB RAM 和 40 GB 磁盘空间,因为每个运行中的 sandbox 都包含完整的 Node 或 Python 工具链,每个工作区还会在磁盘上保留独立的依赖树。主机内存不足时,sandboxd 的压力回收器会停止 sandbox 以释放内存。如果构建任务超过其容器的内存上限,内核会将其终止:docker ps -a 会显示退出代码 137。

sandboxd 与 Dify 或 OpenHands 有什么区别?

它们生成的产物不同。Dify 构建的是运行时调用模型的应用,例如聊天界面和检索流水线。OpenHands 会修改现有仓库,运行命令并为现有代码提出更改。sandboxd 则根据提示创建全新的项目,在其独立容器中构建项目,并通过预览 URL 提供服务。生成的结果是普通 Web 应用,运行时不需要模型。

agent 编写的代码实际存储在哪里?

代码存储在主机文件系统中,而不是容器镜像内。每个应用都会在 /var/lib/sandboxed/workspaces/<id>/ 中获得一个目录,该目录会绑定挂载到其 sandbox 中,文件在 sandbox 内显示为 /home/sandbox/workspace/app。控制平面状态是同一数据目录下 state/ 中的一个 SQLite 文件。您可以从控制台的 Git 选项卡,或通过 /v1/apps/<id>/git/commit/git/push 端点,将代码提交并推送到 git 远程仓库。私有远程仓库所需的令牌由控制平面加密存储,不会交给 sandbox。

将 sandboxd 暴露到互联网是否安全?

可以暴露预览 URL 和控制台,但不要暴露控制平面 API。该 API 会在主机上操作 Docker,因此权限等同于 root;出于这一原因,它默认绑定到 127.0.0.1:9090。在自行托管的构建中,sandbox 还可以向外发起网络连接。这意味着 agent 编写的代码可以访问本地网络和云元数据端点。如果主机所在网络中有需要保护的其他系统,请添加主机防火墙规则。对于来自不受信任用户的提示,应为每个租户使用一台独立主机,而不要仅依赖容器隔离。

#sandboxd#ai-agents#self-hosted#app-builder#Docker