sandboxdをVPSにセルフホストする方法
Docker対応Linux VPSでsandboxdを動かす手順を解説します。固定バージョンでの導入、モデルキー、HTTPSプレビューURL、最低RAM・ディスク容量、古いsandboxの削除方法を確認できます。
sandboxdとは何か、自分で実行すると得られること
sandboxdをセルフホストするには、Dockerを備えたLinuxサーバーとドメイン名が1つ必要です。プロンプトを送ると、コーディングエージェントが隔離されたコンテナ内で実際のアプリケーションを構築し、そのアプリケーションが専用のプレビューURLで起動します。プロンプトからアプリケーションを構築するサービスは2026年に最も注目を集めるホスティング分野であり、sandboxdはVPS上で実行でき、MITライセンスで提供され、生成されたコードを自分のディスクに保存できる製品です。
この設計は、意図的に小規模にまとめられています。GoのコントロールプレーンがDockerを操作し、Traefik v3がすべてのプレビュー用ホスト名をルーティングし、SQLiteが状態を保持します。各アプリケーションは1つのコンテナ内で実行されます。Kubernetesも個別のデータベースサーバーも必要ないため、2 vCPUのサーバーでも実行できます。
全体のモデルは4つのオブジェクトで構成されます。appは永続的なプロジェクトで、名前、gitのメタデータ、Secretを保持します。sandboxはそのappを実行するDockerコンテナであり、1つのappは同時に1つのsandboxを参照します。workspaceはappのファイルで、ホスト上に保存され、コンテナを停止しても残ります。taskはsandbox内のエージェントに渡す1つのプロンプトです。sandboxを停止するとメモリが解放され、ファイルは保持されます。sandboxを破棄するとコンテナは削除され、appは新しいsandboxで起動できます。
sandboxd は Dify や OpenHands とどう違いますか?
この3つは、いずれもサーバー上で LLM(大規模言語モデル)を実行するため、混同されがちです。ただし、生成するものは異なります。Dify は LLM アプリケーションを構築します。チャットインターフェース、検索パイプライン、利用者が使うたびにモデルを呼び出すワークフローなどです。モデルは完成した製品の一部です。OpenHands は既存のリポジトリを対象に動作します。コードを指定すると、ファイルを読み取り、コマンドを実行し、変更案を提示します。sandboxd は何もない状態から始めます。プリセットからプロジェクトのひな形を作成し、新しいコンテナ内でビルドして、確認用の URL を提供します。生成されるのは通常の React または FastAPI アプリケーションで、実行時にモデルを必要としません。
そのため、最終的に何が必要かで選んでください。sandboxd は、文章から始めて、その後もコードを保持したい場合に適しています。残りの2つは、リポジトリまたはモデルを利用する製品がすでに存在する場合に適しています。
もう1つの違いは経過年数です。実際のシステムを構築する前に、この点を評価する必要があります。
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 月時点で開発開始から2か月です。一方、OpenHands は 2024 年 3 月、Dify は 2023 年 4 月に始まっています。リリース v0.1.0 は 2026 年 6 月 6 日、v0.3.6 は 2026 年 8 月 1 日に公開されました。プロジェクトは自らをベータ版と位置付けており、0.x リリースでは互換性が壊れる可能性があると説明しています。これらの数字は品質の判断ではなく、依存関係に伴うリスクとして読んでください。開発開始から2か月のプロジェクトでは、他の利用者がバグを見つける期間も2か月しかありません。
サーバーに必要なリソースと、不足した場合に発生する問題
プロジェクトの説明では、開始時に 2 vCPU と 4 GB の RAM があれば十分です。これはコントロールプレーンと小規模な sandbox 1 つを動かす場合には正しいですが、2 人が同時にビルドするには不足します。メモリは用途ごとに見積もってください。Traefik と Go のコントロールプレーンは小さな構成です。実行中の各 sandbox には Node または Python のツールチェーン全体が保持され、ピークは npm install の後に production build を実行する時点です。複数のアプリを稼働させ続けるサーバーには 8 GB を想定してください。swap は容量ではなく安全網として扱います。swap が発生するビルドは、数秒ではなく数分かかるためです。
メモリが枯渇すると、見た目が大きく異なる 2 つの障害が発生します。sandbox 内では、コンテナが sandboxd の設定したハード --memory 上限に達し、kernel が最大のプロセスを kill します。そのため、agent から有用なメッセージがないままビルドが失敗します。docker ps -a では、そのコンテナの終了コード 137 が表示され、docker inspect を実行すると "OOMKilled": true が報告されます。この方法で停止した Node build では、多くの場合、最初に JavaScript heap out of memory が出力されます。
2 つ目の障害は host で発生します。sandboxd は host のメモリが少なくなると sandbox を停止する pressure reaper を実行します。そのため、小規模なサーバーでは、preview を監視している最中に sandbox が消えることがあります。ファイルは安全で、preview URL への次のリクエストで sandbox は起動します。ただし、コンテナの停止時に実行中だったタスクは再開されません。
より気付きにくい問題はディスクです。各アプリは host 上に独自の workspace を保持し、JavaScript プロジェクトには数百 MB の node_modules ツリーが含まれます。10 個のアプリでは、images を含める前から依存関係だけで数 GB になります。最初は 40 GB から始め、次のコマンドで監視してください。
docker system df
sudo du -sh /var/lib/sandboxed/workspacesデフォルトの data directory は /var/lib/sandboxed です。余分な e が入った綴りです。/var/lib/sandboxd と入力すると空の directory が表示され、原因が分かりにくいまま 5 分を費やすことになります。
固定したsandboxdリリースをインストールする
Docker EngineとCompose pluginに加えて、gitを先にサーバーへインストールする必要があります。VPSへのDockerのインストールで、その手順を説明しています。
docker compose version
git --versionどちらのコマンドもバージョンを表示する必要があります。docker: 'compose' is not a docker commandと表示される場合は、古いスタンドアロンのdocker-composeバイナリを使用しています。インストーラーはv2 pluginを想定しています。
インストーラーはネットワーク経由で取得する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.shSANDBOXD_REFは、インストーラーが$HOME/.sandboxd/srcへcheckoutするgit refです。デフォルト値はmainです。未設定のままにすると、その日の朝にmergeされた内容でインストールされます。2026年7月だけで6回のリリースが行われたプロジェクトでは、この違いが重要です。バージョンを固定し、changelogを確認してから、意図的にアップグレードしてください。
このscriptはsourceをcloneし、imageをbuildし、docker compose up -dでstackを起動します。最後にconsole URLとAPI tokenを表示します。そのtokenを安全な場所に保存してください。これは、rootとしてDockerを操作するAPIのcredentialです。
curl http://127.0.0.1:9090/healthzcontrol planeが起動すると、okが表示されます。何も表示されない場合、stackは起動していません。~/.sandboxd/srcからdocker compose psを実行して停止しているserviceを確認し、続いてdocker compose logs sandboxdで原因を確認してください。
リモートホストのコンソールに接続する
コンソールは http://console.localhost ホスト名で、デフォルトでは HTTP_PORT(80 番ポート)を介して Traefik から提供されます。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 の 2 つのコーディングエージェントが含まれています。SANDBOXD_DEFAULT_AGENT は、使用するエージェントを指定していないタスクでどちらを実行するかを決定し、デフォルトでは opencode を使用します。キーをまったく接続していない場合、タスクは OpenCode Zen のキー不要な無料モデルで実行されます。そのため、費用をかけずに最初のビルドを実行し、支出する前に一連の処理を確認できます。
より高性能なモデルを使用する場合は、自分のキーを接続します。キーはサンドボックスではなくコントロールプレーンに保存されます。データディレクトリ内で暗号化して保存され、credential proxy が通信時に注入するため、エージェントもエージェントが作成したコードもキーを読み取れません。
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 の順に開くと同じ設定ができます。API key の代わりに Claude subscription を使用する場合は、OAuth のガイド付きフローも利用できます。エージェントごとのデフォルトモデルは同じパネルで設定でき、個々のタスクで上書きすることもできます。
エンドツーエンドで小さなアプリを1つ構築する
アプリを作成し、サンドボックスを起動してから、プロンプトを送信します。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が空の場合、サンドボックスは起動していません。通常の原因は、ベースイメージのビルドがまだ完了していないか、ホストのメモリが不足していることです。IDの代わりに401が返る場合は、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)ストリームです。コンソールには、同じストリームがチャットとして表示されます。
アプリはhttp://s-<sandbox id>-3000.preview.localhostで利用できます。3000は指定したポートです。サンドボックスがスリープ状態だった場合、最初のリクエストはTraefikのcatch-allに到達します。sandboxdがコンテナを起動し、ポートが応答するまで待機した後、アプリへ更新される短いウォームアップページを返します。プレビューがそのページから進まない場合、コンテナ内のプロセスがアプリのsandbox.yamlで指定したポートをlistenしていません。
プレビューを実際のドメインで HTTPS 対応にする
各 sandbox には固有のホスト名が割り当てられるため、ワイルドカード DNS レコード 1 件ですべてをカバーできます。A レコードを使用して、*.preview.yourdomain.com をサーバーの IP アドレスに向けます。続いて、~/.sandboxd/src の .env にプレビュー用の変数を設定します。
PREVIEW_DOMAIN=yourdomain.com
PREVIEW_ENTRYPOINT=websecure
PREVIEW_TLS=true
SANDBOXD_API_AUTH_DISABLED=falseTraefik 側でも対応する設定が必要です。traefik/traefik.yml で websecure entrypoint を有効にし、証明書リゾルバーを追加します。DNS-01 challenge を使用してください。ワイルドカード証明書 1 枚ですべてのプレビュー用ホスト名をカバーできるためです。HTTP-01 を使用すると、新しい sandbox ごとに証明書の発行が必要になります。ビルドを集中的に行うと、Let's Encrypt のレート制限にすぐ達します。DNS 側の設定については、DNS-01 challenge によるワイルドカード証明書を参照してください。
cd ~/.sandboxd/src
docker compose up -dプレビュー URL は https://s-<id>-3000.preview.yourdomain.com になります。ファイアウォールでは 80 と 443 を開き、9090 は外部に公開しないでください。ufw の基本的なファイアウォールルールを参照してください。プレビュー用ホスト名を推測できる人は誰でもアプリを読み込めるため、プレビューは公開環境として扱ってください。
生成されたコードはどこに保存されますか。エクスポートもできますか。
ホスト上のデータディレクトリに保存されます。各ワークスペースは /var/lib/sandboxed/workspaces/<id>/ にある通常のディレクトリで、コンテナに bind mount されます。アプリのファイルはサンドボックス内の /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 export は後付けではなく、標準で組み込まれています。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 で一度設定します。これは暗号化して保存され、サンドボックスの外部に置かれます。そのため、agent がこの token を読み取ったり、ユーザーに無断で使用して push したりすることはできません。早い段階から、こまめに push してください。push するまでは、ワークスペースディレクトリがコードの唯一のコピーです。DELETE /v1/apps/<id> を実行すると、取り消しなしで削除されます。
ビルドにはモデルのトークン数でどの程度の費用がかかりますか?
sandboxd は支出を計測しないため、確認すべき数値はプロバイダーのコンソールに表示されるものです。無料の OpenCode Zen モデルは費用がかかりませんが、有料モデルより低速で性能も劣ります。そのため、簡単なアプリを超える作業では修正のラウンド数が増えます。
請求額は、エージェントループの動作に応じて決まります。各ターンで必要なコンテキストを再送するため、費用はアプリの数ではなくターン数に左右されます。1 回のプロンプトで完了すれば安価です。一方、50 個のファイルがあるプロジェクトに対して「次は余白を修正して」と 15 ラウンド繰り返すと、毎回ファイルの内容も送信されるため、高額になります。入力トークンと出力トークンでは料金が異なります。また、コーディングエージェントのセッションごとの費用で現実的な範囲を確認できます。監視なしで実行するループに認証情報を渡す前に、プロバイダー側で支出上限を設定してください。
古いサンドボックスを整理する
アイドル状態のリーパーは、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 を確認してください。休止中のサンドボックスから参照されているイメージは、ガベージではないためです。
コンテナ境界でできることと、できないこと
各サンドボックスは、非特権ユーザーとして実行されます。root ファイルシステムは読み取り専用で、Linux capability はすべて削除され、no-new-privileges が設定されています。メモリ上限とプロセス数の上限も適用されます。プロジェクトは、この境界の限界を明示しています。共有カーネルを使用する Linux コンテナは、強力な分離境界ですが、強力なセキュリティ境界ではありません。カーネルの脆弱性があると、ホストが侵害される可能性があります。
2 つの点には対策が必要です。セルフホスト構成では、サンドボックスからの外向きネットワーク通信が許可されています。そのため、生成されたコードはインターネット、ローカルネットワーク、クラウドのメタデータエンドポイントへアクセスできます。ソースには nftables の外向き通信制御サブシステムがありますが、Portable Docker Compose ビルドでは無効にしてコンパイルされています。したがって、通信制限はホストのファイアウォールで設定する必要があります。また、Docker socket を操作するため、コントロールプレーン API は実質的にホストの root 権限を持ちます。デフォルトでは 127.0.0.1:9090 にバインドし、SANDBOXD_API_AUTH_DISABLED は false のままにする必要があります。この API をインターネットに公開してはいけません。
他の人からサーバーにプロンプトを送信させる予定がある場合、この構成だけでは分離が不十分です。プロジェクトは SANDBOXD_RUNTIME=runsc を指定した gVisor を候補として挙げています。gVisor はサンドボックスとホストの間にユーザー空間カーネルを配置しますが、システムコールを多用する処理はおよそ 1.7~4 倍遅くなります。より強力な対策は、テナントごとに 1 台のマシンを割り当てることです。これは、使い捨て VM でコーディングエージェントを実行する場合と同じ考え方です。
2 か月前のプロジェクトを基盤にして構築すべきですか?
個人用のビルドボックスであれば、明確な予防策を講じる限り問題ありません。SANDBOXD_REF を固定し、/var/lib/sandboxed をバックアップし、必要なアプリはすべて git リモートへプッシュしてください。顧客が利用するものについては、1.0 を待つか、破壊的変更への対応を予算に含めてください。メンテナーは、0.x では利用者の意図に反して変更が発生する可能性があると明言しています。メンテナーは 2026 年 8 月時点で月額 79 ドルのマネージドインストールも提供しています。プロジェクトが存続する理由があるかを判断する際には、この点も把握しておく価値があります。
リスクを許容できる理由は、生成物にあります。sandboxd は、通常の git リポジトリに通常のアプリケーションを生成します。そのため、プロジェクトの開発が停滞しても、コードは残り、失うのはラッパーだけです。プロジェクトを所有するホステッドビルダーよりも、はるかに有利な状態です。今年サーバーで運用する価値があるものを広く確認するには、2026 年にセルフホストする価値があるもの を参照してください。
FAQ
sandboxd の最小サーバー要件は何ですか?
プロジェクトによると、開始時点では 2 vCPU と 4 GB の RAM で十分です。これには control plane、Traefik、1 つの小規模な sandbox が含まれます。複数のアプリを同時に稼働させる場合は、8 GB の RAM と 40 GB のディスクを使用してください。実行中の各 sandbox は Node または Python のツールチェーン一式を保持し、各 workspace は独自の依存関係ツリーをディスク上に保持するためです。ホストのメモリが不足すると、sandboxd の pressure reaper が sandbox を停止してメモリを解放します。また、コンテナのメモリ上限を超えたビルドは kernel によって強制終了されます。この場合、docker ps -a に終了コード 137 が表示されます。
sandboxd は Dify や OpenHands とどう違いますか?
生成する成果物が異なります。Dify は、実行時にモデルを呼び出すアプリケーションを構築します。たとえば、チャットインターフェースや検索拡張生成のパイプラインです。OpenHands は、既存のリポジトリを編集し、コマンドを実行して既存コードへの変更を提案します。sandboxd は、プロンプトからまったく新しいプロジェクトのひな型を作成し、専用コンテナ内でビルドしてプレビュー URL で提供します。生成されるのは、実行時にモデルを必要としない通常の Web アプリケーションです。
agent が作成したコードは実際にはどこに保存されますか?
コンテナイメージ内ではなく、ホストのファイルシステム上に保存されます。各アプリには /var/lib/sandboxed/workspaces/<id>/ の下にディレクトリが割り当てられ、これが sandbox に bind mount されます。ファイルは sandbox 内の /home/sandbox/workspace/app に表示されます。control plane の状態は、同じデータディレクトリ内の state/ の下にある 1 つの SQLite ファイルに保存されます。console の Git タブ、または /v1/apps/<id>/git/commit と /git/push の endpoint を使用して、git remote へ commit と push を実行できます。private remote 用の token は、sandbox に渡されるのではなく、control plane によって暗号化されて保存されます。
sandboxd をインターネットに公開しても安全ですか?
公開するのは preview URL と console に限定し、control plane API は公開しないでください。この API はホスト上の Docker を操作するため、root と同等の権限を持ちます。そのため、デフォルトでは 127.0.0.1:9090 に bind します。self-hosted build の sandbox はネットワークへの外向き接続も許可されています。つまり、agent が作成したコードはローカルネットワークや cloud metadata endpoint に接続できます。保護すべき他のホストがある場合は、ホストの firewall ルールを追加してください。信頼できない人からのプロンプトを処理する場合は、コンテナ境界だけに依存せず、tenant ごとに 1 台のホストを使用してください。