SSD Nodes Learn Hosting plans →
指南 Matt Connor作者: Matt Connor · 已更新 2026-08-13

如何在 VPS 自行代管 sandboxd AI App Builder

在自己的 VPS 執行 sandboxd:了解固定版本安裝、模型金鑰、HTTPS 預覽 URL、最低 RAM 與磁碟需求,以及清理過期 sandbox 的方法。

sandboxd 是什麼,以及自行執行它的效益

要自行代管 sandboxd,您需要一台安裝 Docker 的 Linux 伺服器,以及一個網域名稱。您送出提示後,coding agent 會在隔離的容器內建置實際應用程式,該應用程式會使用專屬的預覽 URL 啟動。Prompt-to-app 建置工具是 2026 年最受矚目的代管服務類別,而 sandboxd 可在您的 VPS 上執行,採用 MIT 授權,產生的程式碼也會儲存在您自己的磁碟上。

這項設計刻意保持精簡。Go control plane 會控制 Docker,Traefik v3 會路由每個預覽主機名稱,SQLite 會儲存狀態,而每個應用程式都在一個容器內執行。這裡不使用 Kubernetes,也不需要獨立的資料庫伺服器,因此 2 vCPU 的主機也能執行它。

整個模型由 4 個物件組成。app 是持久的專案,包含名稱、git 中繼資料與 secrets。sandbox 是執行該 app 的 Docker 容器,而一個 app 同一時間只會指向一個 sandbox。workspace 是 app 的檔案,儲存在主機上,即使容器停止也會保留。task 是交給 sandbox 內 agent 的單一提示。停止 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 顆星,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 RAM 足以開始使用。對 control plane 加上一個小型 sandbox 而言,這個估算是準確的;但若有兩人同時建置,就不夠用了。請分項估算記憶體。Traefik 和 Go control plane 的需求都很小。每個執行中的 sandbox 都包含完整的 Node 或 Python toolchain,而尖峰用量會出現在 npm install 接著執行 production build 時。若伺服器要維持數個應用程式運作,請規劃 8 GB 記憶體。swap 應視為安全網,而不是可用容量,因為發生 swap 的 build 需要數分鐘,而不是數秒。

記憶體耗盡時會出現兩種不同的失敗,而且表現完全不同。在 sandbox 內,容器會達到 sandboxd 設定的硬性 --memory 上限,接著 kernel 會終止佔用記憶體最多的程序,因此 build 會失敗,agent 不會提供有用訊息。docker ps -a 會顯示該容器的結束代碼 137,而在該容器上執行 docker inspect 會回報 "OOMKilled": true。以這種方式失敗的 Node build 通常會先輸出 JavaScript heap out of memory

第二種失敗發生在主機上。sandboxd 會執行 pressure reaper,在主機記憶體不足時停止 sandbox。因此,在資源較少的伺服器上,sandbox 可能會在你查看 preview 時消失。檔案不會受影響;下一次對 preview URL 發出請求時,sandbox 會重新啟動。但容器停止時正在執行的工作不會恢復。

磁碟空間是較不明顯的問題。每個應用程式都會在主機上保留自己的 workspace,而 JavaScript 專案的 node_modules tree 可能達到數百 MB。10 個應用程式在尚未計入 images 前,就可能需要數 GB 的相依套件。請從 40 GB 開始,並持續監控:

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

預設資料目錄是 /var/lib/sandboxed,其中拼法多了 e。輸入 /var/lib/sandboxd 會得到空目錄,並因此浪費五分鐘排查問題。

安裝固定版本的 sandboxd

系統必須先安裝 Docker Engine、Compose plugin 和 git。在 VPS 上安裝 Docker涵蓋這部分。

docker compose version
git --version

兩者都必須輸出版本資訊。docker: 'compose' is not a docker command 表示系統使用舊版獨立 docker-compose binary,而 installer 需要 v2 plugin。

installer 是透過網路取得的 shell script,因此執行前應先閱讀內容,並固定版本。

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 是 installer checkout 到 $HOME/.sandboxd/src 的 git ref,預設值為 main。若不設定此值,安裝的版本會取決於當天早上合併的內容。對於僅在 2026 年 7 月就發布 6 個版本的專案,這一點很重要。請固定版本,閱讀 changelog 後再主動升級。

該 script 會複製原始碼、建置 images、使用 docker compose up -d 啟動 stack,最後輸出 console URL 和 API token。請將此 token 儲存在安全的位置。它是可透過 Docker 以 root 身分執行操作之 API 的憑證。

curl http://127.0.0.1:9090/healthz

控制平面啟動後,這會輸出 ok。若沒有輸出內容,表示 stack 未啟動:請從 ~/.sandboxd/src 執行 docker compose ps,確認哪個服務停止,再執行 docker compose logs sandboxd 查看原因。

連線至遠端主機的主控台

主控台透過 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 標頭。首次造訪時,請設定主控台密碼。

為 agent 指定模型

基礎映像檔內建兩個 coding agent:OpenCode 與 Claude Code。SANDBOXD_DEFAULT_AGENT 會決定未指定 agent 的工作要使用哪一個,預設為 opencode。完全未連接任何 key 時,工作會使用 OpenCode Zen 的免 key 免費模型,因此第一次建置不會產生成本,您可以先測試完整流程,再決定是否付費。

需要更強大的模型時,請連接自己的 key。Key 會送至 control plane,不會進入 sandbox:系統會將其加密儲存在 data directory 下,再由 credential proxy 透過網路注入。因此,agent 與其產生的程式碼都無法讀取這些 key。

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 流程。每個 agent 的預設模型位於同一個面板中,單一工作也可以覆寫此設定。

從頭到尾建置一個小型應用程式

建立應用程式、啟動其 sandbox,然後傳送提示。ID 會以 JSON 傳回,quickstart 會使用 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 表示 sandbox 從未啟動,通常原因是 base image 仍在建置,或主機記憶體不足。若回傳的是 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"}'

回應會包含 task ID。GET /v1/sandboxes/$SB/tasks/<task id> 會回傳其結果;同一個 task 的 /events 路徑則是即時 SSE(server sent events)串流,顯示 agent 正在執行的工作。主控台會以聊天形式顯示相同的串流。

應用程式位於 http://s-<sandbox id>-3000.preview.localhost,其中 3000 是你要求使用的埠。若 sandbox 處於休眠狀態,第一個請求會到達 Traefik 的 catch-all。sandboxd 會啟動容器,等待該埠回應,並提供會重新整理至應用程式的簡短啟動畫面。預覽畫面一直停留在該頁面,表示應用程式內的程序沒有監聽 app 的 sandbox.yaml 中所宣告的埠。

將預覽環境放在具備 HTTPS 的正式網域上

每個 sandbox 都有自己的主機名稱,因此一筆萬用 DNS 記錄即可涵蓋全部環境。使用 A 記錄將 *.preview.yourdomain.com 指向伺服器的 IP 位址。接著在 ~/.sandboxd/src 中設定 .env 的預覽變數:

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

Traefik 也需要相應設定:在 traefik/traefik.yml 中啟用 websecure entrypoint,並加入憑證解析器。請使用 DNS-01 challenge,因為一張萬用憑證即可涵蓋所有預覽主機名稱。若使用 HTTP-01,每個新 sandbox 都必須個別申請憑證;繁忙的建置作業很快就會觸及 Let's Encrypt 的速率限制。透過 DNS-01 challenge 設定萬用憑證說明 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。控制平面狀態儲存在 state/sandboxd.db 的單一 SQLite 檔案中,加密的 agent 憑證位於 agent-auth/。沒有任何資料隱藏在容器層中,因此備份只需複製目錄,再備份該資料庫檔案即可。VPS 上的 restic 備份 可同時處理這兩項工作。

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

Git 匯出功能是內建的,不是後加的功能。API 提供 status 和 diff 供讀取,接著可執行 commit 和 push:

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"}'

私有 remote 需要 personal access token。請在主控台的 Settings、Git credentials 中設定一次。該 token 會以加密方式儲存,並留在沙箱之外,因此 agent 無法讀取,也無法在你不知情的情況下使用它執行 push。請及早且經常執行 push。在此之前,工作區目錄是程式碼的唯一副本,而 DELETE /v1/apps/<id> 會直接將其移除,無法復原。

建置需要多少模型 token?

sandboxd 不會計量你的支出,因此應以服務供應商主控台中的數字為準。免費的 OpenCode Zen models 不收費,但速度較慢、能力也比付費 model 弱。對於超出玩具應用程式範圍的工作,這通常會表現為需要更多輪修正。

帳單金額取決於 agent loop 的運作方式。每一輪都會重新傳送所需的內容,因此成本取決於回合數,而不是應用程式數量。一次就能完成的 prompt 成本很低。若針對包含 50 個檔案的專案,反覆進行 15 輪「現在修正間距」的要求,成本就不低,因為每次都會一併傳送檔案內容。輸入與輸出 token 的計價方式不同,而coding agent 每個工作階段的成本則提供較符合實際的範圍。將 loop 交給無人監看的執行環境前,請先在服務供應商端設定嚴格的支出上限。

清理過期的 sandbox

idle reaper 會停止閒置時間超過 SANDBOXD_IDLE_THRESHOLD_SECONDS 的 sandbox。預設值為 2100 秒,也就是 35 分鐘。這會釋放 RAM,但保留檔案;下一次對 preview URL 發出的請求會喚醒容器。若是小型伺服器,應降低此值,因為閒置容器持續 35 分鐘,就代表有 35 分鐘的記憶體無法使用。

停止不等於刪除,磁碟空間通常會在這裡悄悄耗盡。停止的 sandbox 仍會保留其 workspace 與容器。移除 sandbox 但保留 app,會對 sandbox 執行 DELETE,並一併移除容器與 workspace。移除 app 則會永久刪除所有內容。

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 顯示的可回收 image 空間可能比預期更多,因為每個自行下載 toolchain 的 app 都會留下 image layer。docker image prune 會清除未使用的 image layer。先檢查 GET /v1/apps,因為仍由休眠 sandbox 參照的 image 不屬於垃圾。

容器邊界能提供及不能提供的防護

每個 sandbox 都以非特權使用者身分執行,具有唯讀根檔案系統、已移除所有 Linux capabilities、已設定 no-new-privileges、記憶體上限及程序數量限制。此專案明確說明了這些限制:共用核心的 Linux container 是強大的隔離邊界,但不是強大的安全邊界。核心漏洞可能導致 host 遭入侵。

有兩點需要採取措施。自架版本中的 sandbox 可對外建立網路連線,因此產生的程式碼可以連到網際網路、區域網路及 cloud metadata endpoint。原始碼中存在 nftables egress 子系統,但可攜式 Docker Compose 版本將其停用編譯,因此相關限制必須由 host firewall 提供。控制平面 API 實際上具備 host root 權限,因為它會操作 Docker socket。它預設繫結至 127.0.0.1:9090SANDBOXD_API_AUTH_DISABLED 必須維持為 false,而且絕對不應發布到網際網路。

如果你打算讓其他人向你的主機傳送 prompts,單靠這種模型並不足夠。此專案建議使用 gVisor 搭配 SANDBOXD_RUNTIME=runsc。它會在 sandbox 與 host 之間加入 userspace kernel,但系統呼叫密集型工作大約會慢 1.7 到 4 倍。更強的做法是每個租戶使用一台獨立機器,理由與在一次性 VM 中執行 coding agents相同。

應該以一個兩個月前的專案為基礎建置嗎?

如果是個人建置伺服器,可以,但要採取基本的預防措施:固定 SANDBOXD_REF、備份 /var/lib/sandboxed,並將所有重要應用程式推送到 git remote。若是客戶會接觸的環境,請等到 1.0,或預留因變更而故障的成本,因為維護者已明確表示,0.x 版本可能在你使用期間變更。維護者也提供代管安裝服務;截至 2026 年 8 月,費用為每月 79 dollars。評估這個專案是否有持續維護的理由時,這項資訊值得納入考量。

這項風險之所以可以接受,是因為輸出結果可靠。sandboxd 會在一般的 git repository 中產生一般的應用程式。因此,即使專案停止發展,你仍保有程式碼,失去的只有外層包裝。這比由代管建置服務掌控專案的情況好得多。若要了解今年哪些項目值得放在自己的伺服器上,請參閱 2026 年值得自行代管的項目

FAQ

sandboxd 的最低伺服器規格為何?

專案文件指出,2 vCPU 和 4 GB RAM 即可開始使用,足以執行 control plane、Traefik 和一個小型 sandbox。如果要同時執行數個應用程式,建議使用 8 GB RAM 和 40 GB 磁碟空間,因為每個執行中的 sandbox 都會保留完整的 Node 或 Python 工具鏈,而每個 workspace 也會在磁碟上保留自己的相依套件樹。主機記憶體不足時,sandboxd 的 pressure reaper 會停止 sandbox 以釋放記憶體;如果建置程序超過其 container 的記憶體上限,kernel 會將其終止:docker ps -a 會顯示其結束代碼為 137。

sandboxd 與 Dify 或 OpenHands 有何不同?

它們產生的成果不同。Dify 會建立在執行期間呼叫 model 的應用程式,例如聊天介面和檢索管線。OpenHands 會編輯既有的 repository,執行命令並針對現有程式碼提出變更。sandboxd 則會依據 prompt 建立全新的專案,在自己的 container 中完成建置,並透過 preview URL 提供服務;產出的結果是一般 web application,不需要 model 才能執行。

agent 實際寫入的程式碼位於何處?

程式碼位於 host filesystem,而不是 container image 內。每個應用程式都會在 /var/lib/sandboxed/workspaces/<id>/ 取得一個目錄,並將該目錄 bind mount 到其 sandbox;檔案會在 sandbox 內的 /home/sandbox/workspace/app 顯示。Control plane 狀態會以單一 SQLite 檔案儲存在相同資料目錄下的 state/。您可以從 console 的 Git 分頁,或透過 /v1/apps/<id>/git/commit/git/push endpoint,將變更 commit 並 push 到 git remote;私人 remote 所需的 token 會由 control plane 加密儲存,不會交給 sandbox。

將 sandboxd 暴露到網際網路是否安全?

可以暴露 preview URL 和 console,但不要暴露 control plane API。該 API 會在 host 上操作 Docker,因此等同於 root 權限;基於這個原因,它預設會繫結至 127.0.0.1:9090。在 self-hosted build 中,sandbox 也具有開放的對外網路連線能力,表示 agent 寫入的程式碼可以連線到您的區域網路和 cloud metadata endpoint。因此,如果該主機所在網路有其他需要保護的系統,請加入 host firewall 規則。對於不信任來源的 prompt,請為每個 tenant 使用一台獨立主機,不要只依賴 container boundary。

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