SSD Nodes Learn Hosting plans →
ガイド Matt Connor著者 Matt Connor ・更新日 2026-08-28

SandBaseをVPSに自己ホストする方法

SandBase Harness v0.3.2をVPSで動かす手順です。タグ指定のインストール、agent YAML、MCPサーバー、sandboxモード、Anthropic SDKの接続先設定を解説します。

自己ホストで SandBase エージェントランタイムを運用すると得られるもの

SandBase エージェントランタイムを自己ホストすると、自分が管理するサーバー上で SandBase Harness を実行できます。セッション、認証情報、メモリ、監査証跡は他者の環境ではなく、自分のディスクに保存されます。これは Node サービスです。127.0.0.1:3000 で待ち受け、/v1 HTTP API と Web コンソールを提供し、エージェントファイルの隣にある SQLite に状態を保存します。

/v1 API は、ホスト型のマネージドエージェント API である Claude Managed Agents (CMA) を基に設計されています。そのため、このランタイムは両方向で利用できます。Anthropic SDK を使ってコードを作成し、baseURL を自分のサーバーに向けられます。その後、同じコードをホスト型デプロイメントへ移行できます。

SandBase Harness にモデルは含まれていません。モデルを呼び出して利用します。2026年8月時点では、OpenAI、Anthropic、OpenAI互換エンドポイントをサポートしています。これには、自己ホスト型ゲートウェイや DeepSeek V4 などのプロバイダーが含まれます。API key、または OpenAI API に対応するローカルサーバーは、自分で用意する必要があります。

開始前に必要なもの

  • 2 GB 以上の RAM を搭載した Ubuntu 24.04 の VPS。インストールで最も負荷が高いのは TypeScript のビルドです。
  • Node.js 22 以降と npm 10 以降。どちらもプロジェクトが定める最低要件です。
  • git と、使用するモデルプロバイダーの API key。
  • Docker。ただし、セッションごとのコンテナサンドボックスを使用する場合に限ります。

Ubuntu 24.04 の標準リポジトリには Node 18.19 が含まれていますが、最低要件を満たしません。そのため、NodeSource から Node をインストールします。

curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -
sudo apt install -y nodejs git
node -v
npm -v

node -v は v22 以降、npm -v は 10 以降を出力する必要があります。node -v がまだ v18.19.1 を出力する場合は、ディストリビューションのパッケージがインストールされたままで、PATH で優先されています。続行する前に削除してください。ビルドは、シェルが検出した node を使用して実行されるためです。

v0.3.2 タグから SandBase をインストールする

移動するブランチではなく、タグからインストールしてください。main をベアクローンすると、1 時間前に追加された内容が取得される可能性があり、以下の設定キーと一致しないことがあります。2026 年 8 月 16 日時点の現在のタグは v0.3.2 です。

sudo install -d -o "$USER" -g "$USER" /opt/sandbase
cd /opt/sandbase
git clone --branch v0.3.2 --depth 1 https://github.com/sandbaseai/sandbase-harness.git
cd sandbase-harness
npm ci
npm run build

npm install ではなく npm ci を使用してください。ci はコミット済みのロックファイルに記録された正確なバージョンをインストールするため、作業ツリーがメンテナーのテスト済みのツリーと一致します。npm install はより新しいバージョンを解決できるため、固定されたタグが気付かないうちに固定でなくなる原因になります。

次に、ワークスペースを作成します。ワークスペースはエージェントのファイルとすべての実行時状態を保持する独立したディレクトリです。ソースチェックアウトの外部に置くことで、新しいタグを取得してもデータに影響しません。

mkdir -p /opt/sandbase/workspace
cd /opt/sandbase/workspace
node /opt/sandbase/sandbase-harness/dist/index.js init
node /opt/sandbase/sandbase-harness/dist/index.js start

init はワークスペース内に .managed-agents/ ディレクトリを作成します。start は http://127.0.0.1:3000/dashboard でコンソールを、http://127.0.0.1:3000/v1 で API を起動します。どちらもまだラップトップから到達できませんが、これは正しい状態であり、詳しくは後述します。当面は SSH 経由でコンソールに接続してください。

ssh -N -L 3000:127.0.0.1:3000 you@your-server

node .../dist/index.js の長いパスは扱いにくいため、名前を付けます。

alias sandbase='node /opt/sandbase/sandbase-harness/dist/index.js'

以下のコマンドは、その前提で sandbase <command> として記載しています。

npm からインストールしないでください

このプロジェクトのインストールドキュメントには、次のように記載されています。npm で公開されているスコープなしの managed-agents パッケージは、このプロジェクトのものではありません。そのため、npx managed-agents と npm install -g managed-agents を実行すると、必要なランタイムとは無関係のものが取得されます。メンテナーが公式のスコープ付きパッケージを告知するまでは、タグ付けされた GitHub ソースからインストールしてください。これはプロジェクトの履歴における小さな注記ではありません。v0.3.1 は主に、従来の npm によるクイックスタートを、タグを固定したソース取得手順に置き換えるために存在します。

ワークスペースをモデルプロバイダーに接続する

init は .managed-agents/config.yaml を書き込みます。ワークスペース全体で 1 つのプロバイダーを設定し、個々のエージェントが具体的なモデル ID を選択します。

model:
  provider: openai
  api_key: ${OPENAI_API_KEY}
storage:
  metadata:
    provider: sqlite
    options: {}
  artifacts:
    provider: local
    options:
      base_path: files

${OPENAI_API_KEY} フォームはプロセスの環境変数から値を取得します。そのため、キーを設定ファイルや、そのファイルのバックアップに保存せずに済みます。systemd は権限を降格する前に EnvironmentFile= を root として読み取るため、root だけが読み取れる環境ファイルにキーを記述します。

sudo install -d -m 750 /etc/sandbase
sudo touch /etc/sandbase/runtime.env
sudo chmod 600 /etc/sandbase/runtime.env

そのファイルをエディターで開き、OPENAI_API_KEY=sk-... という 1 行を追加します。プロバイダーキーはここに記述します。エージェント がセッション中に使用する Secret は、ランタイムの認証情報ボールトに保存します。これは影響範囲が異なる別の問題です。どちらかに本番用トークンを貼り付ける前に、AI エージェントから Secret を遠ざける記事を読むことをお勧めします。

エージェントの YAML: mcp_servers、tools、権限ポリシー

エージェントは、ワークスペースの agents/ ディレクトリにある YAML ファイルで定義します。実際にランタイムで時間を使うのはこの部分です。小さなエージェントループを手作業で記述した後なら、各キーの役割を理解しやすくなります。各キーは、本来なら自分で実装するシステムプロンプト、ツール一覧、ツール実行前に行うチェックを設定する項目です。

name: Incident commander
description: Triages alerts and coordinates response.
model: gpt-4o
system: |-
  You are an on-call incident commander.
mcp_servers:
  - name: sentry
    type: url
    url: https://mcp.sentry.dev/mcp
tools:
  - type: agent_toolset_20260401
    default_config:
      permission_policy: { type: always_ask }
    configs:
      - name: bash
        permission_policy: { type: always_ask }
  - type: mcp_toolset
    mcp_server_name: sentry
metadata:
  template: incident-commander

読み込み後、正しく登録されたことを確認します。

sandbase reload
sandbase list
sandbase chat agent_assistant --message "hello"

reload は seed YAML を SQLite にインポートします。list を実行すると、ID 付きでエージェントが表示されるはずです。list に表示されない場合、ファイルは解析されていません。理由は .managed-agents/logs/runtime.log に記録されます。

mcp_servers は MCP(model context protocol)エンドポイントを宣言します。type: url は、ランタイムが別の場所で稼働するサーバーと HTTP で通信することを意味します。そのため、すでに運用しているものをここで利用できます。ランタイムと同じ VPS 上でホストする MCP サーバーも含まれます。

Web 検索は通常、最初に利用するツールです。ただし、独自の SearXNG インスタンスをエージェントに渡す前に、独自の SearXNG インスタンスをエージェントに渡す方法を確認してください。第三者が作成したページを返すツールでは、信頼できないテキストがモデルのコンテキストに直接入るためです。

最初の接続には、より慎重な構成が適しています。自分で管理しているデータに対する読み取り専用エンドポイントを用意する方法です。openGym はワークアウトトラッカー自体の隣にこれを公開します。これにより、エージェントはトレーニング履歴に関する質問には回答できますが、その内容を書き換えることはできません。

サーバーを宣言しただけでは、そのツールはエージェントに渡されません。tools リストで渡します。そこでは、mcp_toolset エントリの mcp_server_name が、上記の name と一致している必要があります。MCP ツールが存在しないかのようにエージェントが動作する場合は、ほかを調べる前に、これら 2 つの文字列を 1 文字ずつ比較してください。

agent_toolset_20260401 は組み込みツールセットです。日付付きのサフィックスはスキーマのバージョンです。そのため、これに固定したエージェントは、作成時に想定したツール定義を使い続けます。default_config はセット内のすべてのツールに適用するポリシーを設定します。configs 配下の各エントリは、ツール名で個別のツールを上書きします。例では bash が該当します。

permission_policy は、単純なモデル呼び出しではなくランタイムを使う価値が現れる部分です。always_ask はセッションを一時停止し、実行前に人間が呼び出しを承認するまで待機します。always_allow は呼び出しを許可します。bash を always_ask に設定すると、正確なコマンドを先に確認しない限り、エージェントは shell コマンドを実行できません。これは、VPS 上で Claude Code を安全に実行する際に利用する制御と同じです。DeepSeek Harness も実行する場合、同じ制御が YAML のキーではなく add-on として提供されます。予算を制限し、ツール呼び出しを制御するプラグインが、このブロックに最も近い機能です。

3 つの sandbox モードと、それぞれに適した状況

コードを実行する tool call は sandbox 内で実行されます。バックエンドは、環境の config オブジェクトにある sandbox_provider、またはコンソールの Settings、Sandbox から環境ごとに選択します。環境は API の POST /v1/environments で作成します。

local は、ホスト上で runtime の子プロセスとして、runtime と同じユーザーでコードを実行します。これがデフォルトです。自分だけが使用し、agent が自分の所有するファイルだけを読み取る場合は、local で問題ありません。ただし、これは分離ではありません。ファイルを削除する tool call は自分のファイルを削除し、/etc/sandbase/runtime.env を読み取る tool call は provider key を読み取ります。

docker は、セッションごとに 1 つのコンテナを起動します。

{
  "sandbox_provider": "docker",
  "image": "node:22-slim",
  "resources": { "memory": "1g", "cpu": 1 }
}

セッションには専用のファイルシステム、メモリ上限、CPU share が割り当てられ、セッション終了時にコンテナも削除されます。自分が作成していないコードを agent が実行する場合は、直ちにこのモードへ切り替えてください。runtime のユーザーに Docker socket へのアクセス権が必要であり、docker グループへの所属はホスト上の root 権限と同等になる点がコストです。セッションごとのコンテナは、実行ごとに 1 つのコンテナを使用する self-hosted agent sandbox と同じ構成です。そのため、プロセスが sandbox から脱出した場合に到達できる範囲についても、同じ考え方が適用されます。

kubernetes は、セッションの workload を pod として実行し、kubectl exec と kubectl cp で制御します。runtime image には kubectl が必要です。また、その ServiceAccount には、対象 namespace 内の pod に対して create、delete、get、list、watch を実行する RBAC(role-based access control)権限と、exec subresource への権限が必要です。すでに cluster を運用している場合に限り、このモードを導入する価値があります。

ランタイムが 127.0.0.1 にバインドされるのはなぜですか?

認証が無効な状態で起動するためです。API key が少なくとも 1 つ存在すると、ランタイムは bearer-token 認証を有効にします。しかし、新規の init は API key を作成しません。認証なしのエージェントランタイムは、shell ツールとプロバイダーの key を保持しています。そのため、デフォルト設定で 0.0.0.0 にバインドすると、ランタイムをインターネット上に公開することになります。

ランタイムへの外部アクセスが必要な場合は、バインドアドレスを変更せず、次の 2 つを行います。

まず、認証を有効にします。サービスの環境変数ファイルで MANAGED_AGENTS_API_KEY を設定するか、POST /v1/api-keys で key を作成します。このコマンドは secret_key フィールドを 1 回だけ返し、その後は再表示しません。クライアントはすべてのリクエストで Authorization: Bearer <key> を送信します。1 つの key は 1 つの共有 identity です。チームメンバーごとに分離された sandbox の agent を用意し、プロバイダーの key を 1 つの gateway で保持したい場合は、OneCLI はその構成に対応しています。

次に、前段に reverse proxy を置き、そこで TLS (transport layer security) を終端します。ランタイムは設計上、平文の HTTP を提供し、証明書の処理を別のコンポーネントに任せます。

server {
    listen 443 ssl;
    server_name agents.example.com;

    ssl_certificate     /etc/letsencrypt/live/agents.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/agents.example.com/privkey.pem;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header Connection "";
        proxy_buffering off;
        proxy_read_timeout 3600s;
    }
}

この 2 行は単なる装飾ではありません。proxy_buffering off が必要なのは、セッションが server-sent events (SSE) でストリーミングされるためです。buffering が有効だと、nginx は buffer が満たされるまでレスポンスを保持します。そのため、エージェントの処理中は console に何も表示されず、最後にすべての内容がまとめて表示されます。proxy_read_timeout 3600s も重要です。デフォルト値は 60 秒なので、1 分を超えてストリームが無通信になると、turn の途中で proxy が接続を閉じます。その結果、ランタイムがクラッシュしたように見えます。

ファイアウォールでは 22 と 443 を開放します。3000 は閉じたままにします。proxy は loopback 経由で接続するため、ホスト外部からアクセスできる必要はありません。

自分のホストを Anthropic SDK の接続先にする

ランタイムは CMA 形式の /v1 インターフェースを実装しているため、Anthropic SDK のクライアントは 1 つのフィールドを変更するだけで接続できます。

import Anthropic from '@anthropic-ai/sdk';

const client = new Anthropic({
  apiKey: process.env.MANAGED_AGENTS_API_KEY ?? 'local-dev-key',
  baseURL: 'http://127.0.0.1:3000'
});

Claude Managed Agents のクライアントが送信するベータヘッダーにも対応しています(anthropic-beta: managed-agents-2026-04-01 と anthropic-beta: agent-memory-2026-07-22)。ローカルランタイムでは省略できます。ホスト型デプロイメント向けに書かれたコードを、そのままここで実行するために用意されています。

互換性は高いものの、完全ではありません。インターフェースが存在すると判断する前に、チェックアウト内の docs/api-matrix.md を確認してください。プロジェクト独自の未対応事項が記載されています。これにはクライアント側のカスタムツールも含まれます。カスタムツールは、現在のイベント結果プロトコルでは、名前付き登録が必要です。

通常の HTTP も同様に使用できます。ランタイムが稼働していることを確認するには、これが最も簡単です。

curl -N -X POST http://127.0.0.1:3000/v1/sessions/SESSION_ID/messages \
  -H "Content-Type: application/json" \
  -d '{"content": "Hello", "stream": true}'

正常な応答では、イベントのストリームが継続的に到着します。接続が切れた場合は、ターン全体を再実行せず、最後に受信したイベントから再開します。

curl -N http://127.0.0.1:3000/v1/sessions/SESSION_ID/events/stream \
  -H "Last-Event-ID: EVENT_ID"

この再開可能なストリームにより、ノート PC を閉じてもセッションが維持されます。イベントはサーバーに保存されるため、クライアントは唯一のコピーを保持するのではなく、ログを再生します。

ディスク上で認証情報、メモリ、監査証跡が保存される場所

ランタイムが管理するすべてのデータは、ワークスペース内の .managed-agents/ 配下にあります。

.managed-agents/
├── config.yaml
├── data.db
├── logs/runtime.log
├── files/
├── skills/
├── snapshots/
└── sandbox/
  • data.db には SQLite のメタデータが保存されます。エージェント、セッション、認証情報ボルトのエントリ、メモリストアのエントリ、API keys が含まれます。
  • files/ にはアップロードされたファイルのデータが保存され、skills/ にはアップロードされた skill packages が保存されます。
  • snapshots/ にはセッションのワークスペーススナップショットが保存され、sandbox/ には local-mode セッションの作業ディレクトリが保存されます。
  • logs/runtime.log は、何もエラーを出さずに処理が行われない場合に最初に確認する場所です。

Credential vault は複数の secret をまとめたもので、environment_variable などの auth_type を使って追加し、セッション作成時に vault_ids でセッションへ関連付けます。Memory store には名前付きのエントリを保存します。各エントリは、固有のアクセス設定と指示を持つ memory_store としてセッションにマウントします。どちらも data.db に保存されます。これが、単純な model call との明確な違いです。ランタイムはセッションをまたいで状態を保持し、実行内容を記録します。

1 つのディレクトリにまとまっているため、ディレクトリ全体を 1 つの単位としてバックアップしてください。

sudo systemctl stop sandbase
sudo tar czf /root/sandbase-$(date +%F).tgz -C /opt/sandbase/workspace .managed-agents
sudo systemctl start sandbase

最初にサービスを停止してください。ランタイムが SQLite データベースへ書き込み中にコピーすると、リストア時に開けないファイルが作成される可能性があります。その事実に気付くのは、バックアップが必要になったときです。agent YAML を git で管理し、状態を別の場所に保存する場合は、デプロイメントドキュメントに従って start 上の --data-dir で状態の保存先を固定できます。

リストアは逆の手順です。新しいホストで同じ tag を checkout し、アーカイブをワークスペースに展開して、サービスを起動します。${OPENAI_API_KEY} 形式を使用した場合、provider key はアーカイブに含まれません。必ず別の安全な場所に保管してください。

systemd で実行する

ランタイム専用のユーザーを作成します。ローカルサンドボックスモードでツールが呼び出された場合でも、そのユーザーとして操作されます。

sudo adduser --system --group --no-create-home --home /opt/sandbase sandbase
sudo chown -R sandbase:sandbase /opt/sandbase

これを /etc/systemd/system/sandbase.service として保存します。

[Unit]
Description=SandBase Harness runtime
After=network-online.target

[Service]
User=sandbase
Group=sandbase
WorkingDirectory=/opt/sandbase/workspace
EnvironmentFile=/etc/sandbase/runtime.env
ExecStart=/usr/bin/node /opt/sandbase/sandbase-harness/dist/index.js start --host 127.0.0.1 --port 3000
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target

プロジェクト独自のデプロイ例では、PATH 上の managed-agents バイナリを呼び出します。タグ付きソースからインストールしてもこのバイナリは作成されないため、ExecStart は代わりにビルド済みのエントリーポイントに対して node を実行します。

sudo systemctl daemon-reload
sudo systemctl enable --now sandbase
sudo systemctl status sandbase
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:3000/dashboard

正常な結果は、status からの active (running) と curl からの 200 です。それ以外の場合は、まず journalctl -u sandbase -n 50 を読み、次に .managed-agents/logs/runtime.log を読みます。重要なのは enable --now の部分です。手動で起動したプロセスは、次回の再起動後には終了しているためです。

発生する問題と表示されるメッセージ

npm run build が npm からエラーを出さずに強制終了される。 1 GB の VPS では、TypeScript のコンパイルが kernel の out-of-memory killer によって停止されます。この情報は npm ではなく kernel log に記録されます。強制終了された node process の名前を含む行を出力する journalctl -k | grep -i "out of memory" で確認できます。swap を追加するか、より大きな instance で build して dist/ をコピーしてください。

Error: listen EADDRINUSE: address already in use 127.0.0.1:3000。 別の process がすでに port を使用しています。sudo ss -lntp | grep 3000 でその process を特定できます。その process を停止するか、--port 3001 を指定して runtime を起動し、proxy を更新してください。

ノート PC から dashboard を読み込めない。 runtime は loopback に bind するため、これは意図した動作です。上記の SSH tunnel を使用するか、reverse proxy の設定を完了してください。--host 0.0.0.0 で修正してはいけません。key が存在するまで authentication は無効だからです。

Docker sandbox が permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock で失敗する。 sandbase user が docker group に所属していません。sudo usermod -aG docker sandbase で修正して service を再起動してください。ただし、付与した権限の内容を理解しておく必要があります。この group は host 上の root に相当するため、runtime 専用 user を設定した理由の一部が失われます。

Kubernetes sandbox が Error from server (Forbidden) で失敗する。 ServiceAccount に pod 権限または exec subresource がありません。kubectl auth can-i create pods/exec -n <namespace> で直接確認してください。結果は yes または no になります。

API key を追加すると、すべての request が 401 を返す。 最初の key が存在すると authentication が有効になり、console と API の両方に適用されます。Authorization: Bearer <key> を送信してください。key を失った場合は、別の key を作成してください。secret_key は 1 回だけ返され、読み取り可能な形式では保存されないためです。

MCP server の tools が session に表示されない。 tools block 内の mcp_server_name を mcp_servers 内の name と照合してください。次に、curl -i <url> で runtime が server 自身から URL に到達できることを確認します。URL type の MCP server は network dependency です。VPS では、ノート PC とは名前解決と network traffic の routing が異なります。

FAQ

OpenAI または Anthropic のキーなしで SandBase Harness を実行できますか?

OpenAI 互換エンドポイントがあれば実行できます。ランタイムは OpenAI、Anthropic、OpenAI 互換プロバイダーに対応しているため、OpenAI API に対応するローカルサーバーを使用できます。.managed-agents/config.yaml でワークスペースのプロバイダーを設定し、api_key とエンドポイントをそのサーバーに指定します。ランタイム自体にはモデルが含まれていないため、呼び出しに応答するモデルが必要です。

ランタイムをパブリックポートで公開しても安全ですか?

インストール直後の状態では安全ではありません。127.0.0.1:3000 に bind し、認証を無効にした状態で起動するため、別の bind アドレスに変更するだけでは解決しません。API キーを作成するか、MANAGED_AGENTS_API_KEY を設定して bearer token 認証を有効にします。次に、TLS 用に nginx または Caddy を前段に置き、ファイアウォールで port 3000 を閉じます。外部からの経路はプロキシ経由だけにします。

local、Docker、Kubernetes の sandbox にはどのような違いがありますか?

local は、ホスト上でランタイムの子プロセスとして tool code を実行します。ランタイムユーザーの権限で動作し、分離されません。docker は、各 session に専用の container を割り当てます。container には専用の filesystem、memory limit、CPU share があり、session の終了時に削除されます。kubernetes は session を pod として実行し、kubectl exec で制御します。ランタイムイメージ内に kubectl が必要で、対象 namespace の pod と exec subresource に対する RBAC も必要です。

具体的に何をバックアップする必要がありますか?

ワークスペース内の .managed-agents/ directory をバックアップします。ここには config.yaml、agent、session、credential vault entry、memory entry を格納する data.db SQLite database のほか、uploaded file、skill package、session snapshot が含まれます。コピー中に SQLite へ書き込まれないよう、アーカイブを作成する前に service を停止します。${OPENAI_API_KEY} として参照される provider API key はバックアップに含まれないため、別途保管してください。

main ではなく v0.3.2 tag を clone するのはなぜですか?

tag は固定された tree です。そのため、説明で確認した config key と CLI command を実際の環境でも使用できます。main は変更されるため、guide の作成時から実行時までの間に config key の名前が変更される可能性があります。また、プロジェクトは、npm 上の scope なしの managed-agents package はこのプロジェクトではないと警告しています。そのため、npx managed-agents を実行すると無関係なものが install されます。Release v0.3.1 は主に、npm の簡易な quick start を、tag を固定した source からの手順に置き換えるために存在します。