OpenBot AI coworkerをVPSで自己ホストする方法
OpenBotを自己ホストし、AI coworkerごとに専用containerとChromiumを割り当てます。gatewayが全操作をポリシー照合・監査する仕組みと、ブラウザーごとに増えるRAM使用量を解説します。
自己ホストする OpenBot AI coworker の構成
OpenBot AI coworker は、管理下のハードウェア上で 1 台のゲートウェイサーバーと、bot ごとに 1 つのコンテナを実行して自己ホストします。各 bot コンテナには専用の Chromium ブラウザーと専用のワークスペースボリュームがあり、セッション間で保持されるブラウザープロファイルを使用します。bot がコンピューター、ファイル、MCP (model context protocol) サーバー、または UI コンポーネントに対して行うすべての操作は、このゲートウェイを経由します。ゲートウェイは操作の実行前にポリシーと照合し、実行後に記録します。ゲートウェイがラップするエージェントループにまだ慣れていない場合は、AI エージェントをゼロから学ぶ の段階的な手順に従い、bot にブラウザーやログイン情報を渡す前に小さなエージェントループを自分で作成できます。
OpenBot は CopilotKit が MIT license で github.com/CopilotKit/openbot に公開しています。最初のタグ付きリリース v0.0.1 は 17 August 2026 に公開されました。プロジェクト自身は alpha 版であり、活発に開発中だと説明しています。設計としては本格的ですが、初期段階特有の未成熟な部分があるものとして扱ってください。
このアーキテクチャーの興味深い点は、コストが大きい点でもあります。エージェントごとにブラウザーを用意するとメモリを消費します。この点を見落とす人が多いため、ここではインストールの前にサイジングを行います。
ゲートウェイが各アクションを決定する仕組み
port 3001 の API server は、bot のコンピューターへ到達する唯一の経路です。ブラウザーアクションを実行する前に、gateway はページスナップショットから対象を解決し、コンテキストに対して CEL(common expression language)のポリシールールを評価し、判定を記録した監査行を書き込みます。その後にのみ container を呼び出します。この処理の後に実行が失敗した場合は、2 行目を記録します。ドキュメントでは、この境界を明確に示しています。コンピューターはポリシーを決定せず、server gateway がアクションの境界になります。この分離には OpenBot の外部で名称があります。ループ、tool 定義、権限チェック、session state がまとまって model を取り囲む harness を構成し、この gateway はその権限部分に当たります。
ポリシーはデフォルトで拒否され、deny rule は allow rule より先に評価されます。重要なのは、ルールの構文よりも失敗時の動作です。ポリシーがない場合は何も許可されません。また、deny rule と allow rule のどちらであっても、壊れたルールはブロックする方向に失敗します。そのため、ポリシーの誤りによって発生するのは、アカウントを自由に操作する bot ではなく、停止した bot です。この層が制御するのは bot の動作であり、bot が読む内容ではありません。そのため、agent 向けの指示を含むページは別の問題として残ります。これは、自分の SearXNG instance の結果を agent に渡す場合にも発生する、同じ prompt injection の攻撃対象です。
監査証跡は PostgreSQL に保存されるため、再起動後も残ります。制御の引き継ぎは computer.help_requested、computer.control_taken、computer.control_released として記録されます。これにより、bot が人間の操作を求めたことと、人間が制御を返したことを確認できます。Secret は値ではなく文字数として記録されます。ファイル操作では、内容ではなくパスとサイズが記録されます。ブラウザーを介さずに同じ制御境界を設ける場合は、AI agent のアクションを承認の背後で制御するで、より限定されたケースを扱っています。
RAM とディスクにおける 1 Bot のコスト
このプロジェクトは、arm64 上で 1 つの Bot を実行した場合の実測値を公開しています。OpenBot が提供しているサイジング情報はこれだけです。また、1 つのアーキテクチャ上で 1 つの Bot を実行した場合の値なので、容量計画ではなく出発点として扱ってください。
The data behind this chart
[
{
"label": "Measured, one Bot",
"memory_gb": 0.55,
"disk_gb": 5.3,
"vcpu": 0.06
},
{
"label": "Documented minimum",
"memory_gb": 2,
"disk_gb": 8,
"vcpu": 1
},
{
"label": "Documented recommended",
"memory_gb": 4,
"disk_gb": 10,
"vcpu": 2
}
]1 つの Bot のピークメモリは 0.55 GB でした。一方、文書化されている最小値は 2 GB、推奨値は 4 GB です。実測値と最小値の差は、負荷時に Chromium が使用するメモリの増加分です。ブラウザーのメモリ使用量は、プロセスが待機中かどうかではなく、開いているページに応じて変わるためです。アイドル時の CPU 使用量はほぼゼロで、実測範囲の最大値でも 0.06 コアです。つまり、確保すべきリソースは CPU ではありません。ディスクです。イメージだけで 5.3 GB あり、推奨ボリュームは 10 GB です。Chromium に加えて Playwright の Firefox と WebKit のバイナリも含まれるため、これほど大きくなります。
これらの値だけでは、複数の Bot を同時に実行した場合のコストは分かりません。プロジェクトもその値を公開していません。文書化された最小値は、プロジェクトが提示できると判断した数値であり、負荷時に実際に監視した値とは限りません。そのため、PhotoPrism と Immich のどちらを選ぶかも、公開値ではなく実測された RAM の下限で判断することになります。実環境で測定してください。まず 1 つの Bot を起動し、ページを開いた実際のタスクを実行させます。その動作中にコンテナを監視してください。
docker stats --no-stream
free -mBot のコンテナについては MEM USAGE 列の値を 1 Bot あたりの値として使用します。そこにゲートウェイと PostgreSQL の分を加え、同時に存在させる予定の Bot 数を 1 Bot あたりの値に掛けます。アイドル状態の Bot もブラウザプロセスを保持するため、この乗算は実行中の Bot だけでなく、存在するすべての Bot に適用します。この計算方法は、コーディングエージェント用 VPS の RAM と CPU のサイジングで使用したものと同じです。ブラウザー側の要件については、VPS 上でエージェント用のヘッドレスブラウザーを実行するで説明しています。
小規模なプランでは、Chromium の設定も影響します。OpenBot は --disable-dev-shm-usage を指定して Chromium を起動するため、ブラウザーは /dev/shm ではなく /tmp に書き込みます。これにより、/dev/shm が小さいホストで発生するクラッシュを回避できます。一方で、負荷が root ファイルシステムに移るため、推奨ディスク容量がイメージ容量より大きい理由がもう 1 つ増えます。
VPS で OpenBot をセルフホストする方法
Docker、Bun 1.3 以降、CopilotKit Intelligence プロジェクト、モデル API key が必要です。開発ドキュメントでは、サーバー上に lsof、python3、curl も必要です。main ではなくタグ付きリリースを clone してください。alpha プロジェクトの main は予告なく変更されるためです。
git clone --branch v0.0.1 https://github.com/CopilotKit/openbot.git
cd openbot
cp .env.example .envIntelligence プロジェクトをプロビジョニングします。次の3つのコマンドで、実行時 key と license token を環境ファイルに書き込みます。
npx --yes copilotkit@latest login
npx --yes copilotkit@latest project select
npx --yes copilotkit@latest license --write保存済み credentials を暗号化する key を生成し、出力を .env に KEY_ENCRYPTION_KEY として設定します。同じファイルに OPENAI_API_KEY を追加するか、BOT_PROVIDER を anthropic または google に設定し、対応する key を指定します。
openssl rand -base64 32続いてインストールして起動します。
bun install
bash scripts/start.shscripts/start.sh は Docker services を起動し、database migrations を実行し、server と app を起動して、それらの health を確認します。完了すると、app は port 3010、API は port 3001 で応答します。スクリプトは port の競合を報告し、同じ service がすでに実行中であればそのまま維持するため、2回実行しても安全です。
外部公開する前に、まず server 自身から確認します。
curl -sS -o /dev/null -w '%{http_code}\n' http://127.0.0.1:3010
ss -ltnp | grep -E ':(3010|3001|4100|4500|5432)'最初のコマンドの 200 は、app が応答していることを示します。2つ目のコマンドは、これらの port がどの address に bind されているかを表示します。VPS では、この結果が重要です。127.0.0.1:3001 と表示される行は、そのサーバー内からのみアクセスできます。0.0.0.0:3001 と表示される行は、server へ route できるすべての相手からアクセスできます。
単一コンテナイメージ
デプロイメントドキュメントには、アプリ、API、Chromium を含む単一イメージも用意されています。このイメージは port 3001 でサービスを提供します。
docker build -t openbot .
docker run -p 127.0.0.1:3001:3001 --env-file .env \
-e EMBEDDED_POSTGRES=on -v openbot-data:/var/lib/postgresql/data openbotEMBEDDED_POSTGRES=on はコンテナ内で PostgreSQL を実行し、起動時にマイグレーションを適用します。名前付きボリュームにより、再デプロイ後も監査履歴が保持されます。これがない場合、再ビルドのたびに履歴が失われます。DATABASE_URL の接続先をマネージドデータベースに変更する場合は、そこでも vector extension を有効にする必要があります。RDS、Cloud SQL、Azure Database などのマネージドサービスはこの extension をサポートしていますが、いずれも自動では有効化しません。そのため、新しいマネージドデータベースに対するマイグレーションは、vector column type がまだ存在しないため失敗します。
データベースが外部にある場合は、release step としてマイグレーションを実行します。
docker run --rm --env-file .env openbot \
sh -c "cd /app/server && bun x drizzle-kit migrate --config=drizzle.config.ts"このイメージでは、ブラウザの port を意図的に公開していません。また、supervisor も含まれていません。supervisor には Docker socket が必要ですが、serverless platform では Docker socket が提供されないためです。supervisor がない場合、すべての bot が 1 つのブラウザと 1 組のログイン情報を共有します。そのため、bot ごとのコンテナ実行によって得られる分離が失われます。bot ごとに別のログイン情報が必要な場合は、COMPUTER_SUPERVISOR_URL と SUPERVISOR_TOKEN を設定して compose stack を実行してください。ただし、このトレードオフを受け入れられるホストで実行します。Docker socket と通信できるプロセスは特権コンテナを起動できるため、実質的にホスト上の root になります。このため、コーディングエージェントに使い捨ての VM を割り当てる場合と同じ考え方で、OpenBot 専用のマシンを用意するのが適切です。
OPENBOT_SINGLE_USER がラップトップ向けの設定である理由
.env.example には OPENBOT_SINGLE_USER=true が含まれています。この設定では、すべてのリクエストを 1 人の administrator からのものとして受け付け、サインインを完全に省略します。ラップトップでは、ポートに接続できるクライアントが自分だけなので便利です。VPS では、ポート 3010 に最初に接続した人が、暗号化された認証情報を保存し、アカウントにサインイン済みのブラウザーを操作するシステムの administrator になります。
実行方法として適切なのは 2 つです。OPENBOT_SINGLE_USER=true を維持し、すべてのポートを 127.0.0.1 にバインドして、SSH トンネルまたはプライベートネットワークインターフェース経由でのみアプリに接続します。
ssh -N -L 3010:127.0.0.1:3010 -L 3001:127.0.0.1:3001 you@your-vpsこの場合、アプリは自分のブラウザーで http://localhost:3010 にアクセスできます。これは secure context として扱われるため、サインイン Cookie と、ライブ画面に必要なブラウザー機能の両方が動作します。もう 1 つの方法は、single-user mode を無効にして、実際の identity provider を設定することです。Google、Microsoft Entra、Okta、SAML、OIDC をサポートしています。どの provider でも、32 文字以上の BETTER_AUTH_SECRET、OAuth callback 用の公開 API base URL に設定した BETTER_AUTH_URL、INITIAL_ADMIN_EMAILS、TRUSTED_ORIGINS が必要です。provider の認証情報は完全に設定してください。設定が不完全な provider があると、open access にフォールバックせず、起動が停止します。アカウントを追加する理由が、チーム内の各ユーザーに自分専用のブラウザーではなく自分専用の agent を持たせることであれば、OneCLI は最初からその構成を前提に設計されています。ユーザーごとに sandbox 化された agent を 1 つ配置し、model key は 1 つの gateway で管理します。
アプリに公開名で接続できる場合は、前段に TLS (transport layer security) を配置します。localhost 以外で通常の http:// を使用して配信したページは secure context ではありません。そのため、Secure 属性が付いた Cookie は保存されず、OpenBot のバグのように見える形でサインインに失敗します。
下位ポートをファイアウォールで遮断する
OpenBot のセキュリティノートでは、下位サービスのエンドポイントはトークンで保護されているため、非公開に保ち、ゲートウェイの迂回に使用しないよう説明されています。トークンは第2の防御です。第1の防御は、ポートにまったく到達できないようにすることです。
agent-computer は 4100 で待ち受け、COMPUTER_TOKENを要求します。bot のエンドポイントは 4200 と 4201 で待ち受けます。supervisor はホスト上の 4500 と、そのコンテナ内の 4300 で待ち受けます。PostgreSQL は 5432 で待ち受けます。これらをパブリックインターフェイスに公開してはいけません。シングルユーザー構成では、app と API も公開する必要はありません。
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 22/tcp
sudo ufw enable
sudo ufw status verboseここには、ファイアウォールだけで十分だと考える人が陥りやすい罠があります。-p 3001:3001でコンテナポートを公開すると、Docker は DNAT ルールを追加します。そのため、トラフィックは FORWARD 経路で処理され、ufw のデフォルト拒否が適用される INPUT チェーンを通過しません。ufw statusが Status: activeを表示していても、ポートは開いたままです。マッピング自体で、公開ポートを -p 127.0.0.1:3001:3001 のように loopback にバインドするか、compose ファイルでホストアドレスを設定してください。ufw statusではなく、ss -ltnpで確認します。この罠は OpenBot 固有のものではありません。そのため、このホストで公開している他のすべてのコンテナについても同じ確認を実行してください。90年代のビデオ店として再構築した Jellyfin ライブラリを提供しているコンテナも対象です。
OpenBot はオフライン構成ではありません
デプロイを計画する前に、この点を確認してください。OpenBot は CopilotKit Intelligence プロジェクトに依存しています。このプロジェクトは、永続スレッドと会話メモリをサーバーの外部に保持します。サーバーは起動時に INTELLIGENCE_API_URL、INTELLIGENCE_GATEWAY_WS_URL、INTELLIGENCE_API_KEY、COPILOTKIT_LICENSE_TOKEN を検証します。4 つすべてを同時に設定する必要があり、1 つでも欠けると起動に失敗します。2026 年 8 月時点では無料プランを利用できます。Intelligence 自体もセルフホストできるため、quickstart に示されている手順よりも作業は増えますが、完全にローカルなデプロイも可能です。
モデルが 2 つ目の外部依存関係です。モデルは同梱されていません。BOT_PROVIDER は openai、anthropic、または google を受け付けます。OPENAI_BASE_URL を使うと、OpenAI の接続先を互換性のある任意のエンドポイントに指定できます。トークンを自分のハードウェア内に保持したい場合は、VPS で Ollama を実行して LLM をセルフホストする構成が該当します。ブラウザー操作ではモデルに大きな負荷がかかるため、採用を決める前に、実際のタスクでローカルモデルをテストしてください。
現時点では 1 レプリカで実行する
ゲートウェイはページのスナップショットをサーバープロセスのメモリにキャッシュします。レプリカが 2 つあると、一方のプロセスが取得したスナップショットをもう一方から参照できません。そのため、要素が見つからないというエラーで操作が断続的に失敗し、ランダムに発生しているように見えます。デプロイメントドキュメントでは、1 レプリカで実行し、プラットフォームの最大インスタンス数を 1 に固定するよう明記されています。この制限は、スナップショットのキャッシュ先をデータベースに移行した時点で解除します。それまでは、ボックスを増やすのではなく、ボックスのリソースを増やして OpenBot をスケールします。ボット間の分離は、引き続きボットごとのコンテナで実現します。これは、自己ホスト型エージェントのサンドボックスによって、あるエージェントのミスが他のエージェントに影響しないようにする仕組みと同じです。
障害時の状態と確認できる内容
.envを入力した直後に起動が終了する。 サーバーは何も提供する前に設定を検証します。Intelligence ブロックが不完全である、KEY_ENCRYPTION_KEYがない、または client ID だけが設定されて secret がない OAuth provider がある場合、静かに機能を縮退させず、いずれも起動を停止します。最初のエラーを読み、そのフィールドだけを修正して、再度起動します。
管理対象データベースで migration が失敗する。 vector extension はデフォルトで有効になっていないため、migration が PostgreSQL に認識されない column type に到達します。superuser として接続し、CREATE EXTENSION vector;を実行してから、migration の手順を再実行します。
アプリケーションは読み込まれるが、サインイン状態が維持されない。 公開アドレスでプレーンな http:// を使用しているため、secure context になっておらず、Secure cookie が破棄されます。前段に TLS を配置するか、SSH tunnel を使用してブラウザーから localhost に見えるようにします。
分離する想定だった bot 間で login が共有される。 supervisor が実行されていないため、bot ごとの computer がなく、すべての bot が共有 browser を使用しています。COMPUTER_SUPERVISOR_URLが設定されていることと、supervisor が Docker socket に到達できることを確認します。
bot が停止して助けを求める。 これは設計どおりの動作です。監査証跡に computer.help_requested が記録され、live screen で操作を引き継ぐと、その handover が双方に記録されます。
FAQ
OPENBOT_SINGLE_USER を VPS のデプロイで有効にしたままにしても安全ですか?
ゲートウェイにインターネットから到達できない場合に限り安全です。OPENBOT_SINGLE_USER=true はサインインなしで、すべてのリクエストを 1 人の管理者として受け入れます。そのため、ポートを開ける人は誰でも、デプロイメント、その保存済み認証情報、およびサインイン済みブラウザーを操作できます。すべてのポートを 127.0.0.1 にバインドし、SSH トンネルまたはプライベートネットワークインターフェース経由でアプリにアクセスする構成であれば使用できます。パブリックインターフェースでは無効にし、Google、Microsoft Entra、Okta、または OIDC と BETTER_AUTH_SECRET、BETTER_AUTH_URL、INITIAL_ADMIN_EMAILS、TRUSTED_ORIGINS を構成してください。
OpenBot の Bot 1 つにはどの程度の RAM が必要ですか?
プロジェクトが公開している単一の arm64 向け Bot の数値では、ピークメモリは 0.55 GB です。文書化された最小値は 2 GB、推奨値は 4 GB です。複数の Bot を同時に動かす場合の公開値はありません。各 Bot が専用の Chromium を保持するためです。実際のタスクで Bot 1 つを実行し、docker stats でそのコンテナのメモリ使用量を確認します。ゲートウェイとデータベースの使用量を加え、同時に稼働させる予定の Bot 数を掛けてください。
OpenBot をセルフホストするには CopilotKit アカウントが必要ですか?
はい。OpenBot は永続的なスレッドとメモリに CopilotKit Intelligence プロジェクトを必要とします。Intelligence API URL、ゲートウェイの WebSocket URL、API key、license token をすべて設定しない限り、サーバーは起動しません。2026 年 8 月時点では無料プランを利用できます。Intelligence はセルフホストも可能なため、追加作業によりホステッド依存をなくせます。OpenBot にはモデルが同梱されていないため、独自のモデル API key も用意してください。
なぜ各 Bot に専用のブラウザーが割り当てられ、共有されないのですか?
ブラウザープロファイルは ID だからです。ブラウザーを共有すると Cookie とセッションも共有されます。そのため、1 つの Bot がアカウントにサインインすると、すべての Bot がそのアカウントにサインインした状態になります。Bot ごとのコンテナにより、各 coworker に専用のプロファイルとログイン情報を割り当てられます。代わりにメモリを消費します。Bot ごとに Chromium が動作することが、サイジング上で最大の要素だからです。
OpenBot では、ファイアウォールでどのポートを開けるべきですか?
下位レベルのポートは 1 つも開けないでください。4100 の agent-computer、4200 と 4201 の Bot エンドポイント、4500 の supervisor、5432 の PostgreSQL はすべてプライベートのままにします。プロジェクトはこれらを token で保護していますが、到達不能な状態にしておくことも求めています。人がアクセスする必要のあるものだけを公開してください。また、-p 3001:3001 で公開されたコンテナポートは、ufw の default-deny ルールに関係なく到達可能です。Docker の DNAT ルールによって、そのトラフィックが INPUT ではなく FORWARD の経路に入るためです。