HarnessRouterをセルフホストする方法と初期設定
Codex、Claude Code、Hermesを1つの自前APIで運用します。Dockerでの正確なデプロイ手順、loopback bind、変更必須の初期ログイン、TLS経由の接続方法を確認できます。
HarnessRouter が取り除くもの
HarnessRouter Community Edition をセルフホストすると、自分で管理するサーバー上に複数のエージェントハーネスを配置し、その前段に 1 つの API を置けます。エージェントハーネスは、モデルをループで動かすコマンドラインプログラムです。セッションを維持し、ファイルを編集し、コマンドを実行し、作業を依頼した側へ進行状況をストリーミングします。Codex、Claude Code、Hermes はいずれもこの役割を担いますが、それぞれ独自のインストール方法、認証情報の形式、セッションの扱い方を持っています。HarnessRouter はこれらをすべて 1 つのコンテナ内で実行し、その前段に 1 つの HTTP エンドポイント、1 つのログイン、1 つの Secret ストアを置きます。
これが全体の考え方です。ただし、その代償も明確にしておく必要があります。複数の構成要素を 1 つにまとめるために、サーバーへコンテナ、ログイン、ボリューム、アップグレード手順を追加します。現在 1 つのハーネスだけを使っている場合は、そのハーネスを直接インストールするよりも構成が複雑になります。このトレードオフについては最後のセクションで説明するため、デプロイする前に確認してください。
以下の内容は、19 August 2026 に取得した image tag 0.5.5 を基準に確認しています。プロジェクトはほとんど毎日新しいタグを公開しているため、1 か月後にこのページの内容をそのまま信頼せず、実際に実行するタグを確認してください。コマンドは、プロジェクトの README(github.com/HarnessRouter/harnessrouter)から引用しています。
Unified Harness Protocol の実体
HarnessRouter は、unifiedharnessprotocol.org で公開されている Unified Harness Protocol(UHP)を実装します。UHP は、製品が harness 上でタスクを開始し、実行中のタスクを追跡し、セッションとファイルを管理し、失敗を報告する方法を定義します。仕様は日付でバージョン管理されます。2026 年 8 月 19 日時点で有効なバージョンの日付は 2026-08-11 です。サイトでは、これを「構築の基盤として十分に安定しており、安全に変更できるようバージョン管理された」draft standard と説明しています。
ここでいう「open standard」という表現は、注意して読んでください。同じ企業が仕様、reference implementation、適合性を判定する 52 項目の conformance suite を作成しています。この形態は、登場して間もないプロトコルでは一般的です。また、Apache-2.0 ライセンスにより、その一部または全体を fork できます。一方で、UHP はまだ multi-vendor standard ではありません。新たに登場したプロトコルとして扱ってください。実用的で発展途上にあり、自分のコードを rewrite せずに利用を停止できるようにしておくべきものです。
開始前に必要なもの
Docker と、空きディスク容量をおよそ 4 GB 用意します。すでに料金を支払っているモデルプロバイダーの API key も必要です。イメージの pull には約 700 MB を使用し、残りの容量は agent CLI と、それらが書き込む workspace に使われます。イメージにはモデルも trial key も含まれていないため、プロバイダーを接続するまでタスクは失敗します。HarnessRouter 自体のライセンスは Apache-2.0 です。agent CLI はこのライセンスの対象外です。そのため、イメージに収録せず、初回起動時に取得します。
docker run 1 回で HarnessRouter をセルフホストする
docker pull harnessrouter/harnessrouter
docker run -d --name harnessrouter \
-p 127.0.0.1:3000:3000 \
-v harnessrouter:/data \
harnessrouter/harnessrouter次に、コンテナが起動する様子を監視します。初回起動は時間がかかります。ログを見ると、その理由が分かります。
docker logs -f harnessrouter正常に動作している間、次のような行が表示されます。
installing Claude Code (Anthropic's terms apply)…
installing Codex (Apache-2.0)…
installing Hermes (check its upstream license before use)…ready on :3000 が表示されるまで待ちます。このインストールはボリュームごとに 1 回だけ実行されます。そのため、以降の起動は数秒で完了し、インストール関連の行は一切表示されません。
このダウンロードから分かる重要な点は 2 つあります。どちらも VPS では重要です。1 つ目は、初回起動時に外部ネットワークへの接続が必要だということです。イメージは自己完結していません。そのため、送信トラフィックをフィルタリングする環境や、外部への経路がないホストでは、ここで処理が止まり、ready on :3000 が表示されません。失敗するのは docker pull の時点ではなく初回起動時です。この点に気付くまで分かりにくい場合があります。2 つ目は、サードパーティソフトウェアを、それぞれのライセンス条件に従ってインストールするということです。Claude Code には Anthropic の規約が適用され、Hermes には上流プロジェクトが定める条件が適用されます。商用利用の前に、両方を確認してください。
-v harnessrouter:/data は名前付き Docker ボリュームを作成します。永続化されるデータはすべて /data に保存されます。SQLite データベース、保存ファイル、Secret ストア、エージェントのワークスペースが含まれます。このボリュームを削除すると、プロバイダーのキーやすべてのトランスクリプトを含め、インスタンス全体を削除したことになります。バックアップはコンテナを停止してから実行してください。書き込み中の SQLite データベースをコピーすると、開けないファイルになる可能性があります。同じ停止後コピーの手順は、ホスト上のすべてのステートフルコンテナにも適用します。ただし、詳細はサービスによって異なります。たとえば PhotoPrism と Immich では、それぞれ専用のバックアップコマンドが必要です。
docker stop harnessrouter
docker run --rm -v harnessrouter:/data -v "$PWD":/backup alpine \
tar czf /backup/harnessrouter-data.tgz -C / data
docker start harnessrouterCompose 版と変更が必要な行
リポジトリには compose ファイルが含まれています。このファイルは "3000:3000" を公開するため、ホスト上のすべてのインターフェースで利用可能になります。パブリックサーバーで起動する前に、この行を変更してください。
services:
harnessrouter:
image: harnessrouter/harnessrouter:0.5.5
ports:
- "127.0.0.1:3000:3000"
env_file:
- .env
volumes:
- harnessrouter-data:/data
restart: unless-stopped
volumes:
harnessrouter-data:上流版との違いは、バインドアドレスと、latest ではなく固定したバージョンタグを使用している点です。2026 年 8 月 9 日から 18 日の間に 16 個のバージョンタグが公開されたため、実行中にエージェントのランタイムが変わるとデバッグが困難になります。次に環境ファイルをコピーし、権限を制限してから起動します。
cp .env.example .env
chmod 600 .env
docker compose up -d
docker compose logs -f.env にはプロバイダーのキーが平文で保存されるため、mode 600 が最低限必要です。docker compose サブコマンドに慣れていない場合は、Docker Compose コマンドチートシートで日常的に使用するコマンドを確認できます。
ポートを 0.0.0.0 ではなく 127.0.0.1 で公開する理由
-p 3000:3000 はホストのすべてのインターフェースでポートを公開します。-p 127.0.0.1:3000:3000 は loopback だけで公開するため、接続できるのは VPS 自体からだけです。コンテナは常に内部の 3000 で待ち受けるため、変更するのは左側です。設定を確認します。
docker port harnessrouter
sudo ss -ltnp | grep 3000ss が 127.0.0.1:3000 を出力する設定は正しい状態です。0.0.0.0:3000 はコンソールがパブリックインターネット上にあることを意味します。このコンソールでは、harness の作成、すべての transcript の読み取り、agent の実行に加え、agent に workspace 内の shell と実際のファイルシステムへのアクセスを許可するため、一般的な self-hosted アプリケーションよりも危険です。接続した provider key も保持しています。保護されていないコンソールに到達できる攻撃者は、作業内容を読み取り、コマンドを実行し、key を使って費用を発生させられます。
ホストの firewall だけでは防げません。Docker は独自のルールを kernel の nat テーブルに書き込みます。これらのルールは、ufw が管理する chain より先に評価されるため、sudo ufw status で拒否と表示されても、公開したポートには到達できます。VPS ではなく別のマシンからテストしてください。VPS からテストすると、何も検証できません。これは 3080 番ポートで dsh をヘッドレス実行する 場合と同じ教訓です。サービスを loopback に bind し、その後で接続方法を意図的に決めます。
最初にデフォルトのログイン情報を変更する
http://localhost:3000でユーザー名harnessrouter、パスワードharnessrouterを使用してサインインします。これらの認証情報は秘密情報ではなくプレースホルダーであるため、README に記載されています。変更するまで、コンテナの起動時に毎回警告が表示されます。
using the DEFAULT password. Set HR_AUTH_PASSWORD, or change it from the profile page, before exposing this instance.Profile ページで変更するか、スクリプトによるデプロイでは起動時に設定します。HR_AUTH_USERとHR_AUTH_PASSWORDでデフォルト値を上書きできます。
docker run -d --name harnessrouter \
-p 127.0.0.1:3000:3000 \
-v harnessrouter:/data \
-e HR_AUTH_USER='you' \
-e HR_AUTH_PASSWORD='the-password-you-chose' \
harnessrouter/harnessrouterアカウントシステムとメールサーバーがないため、パスワードのリセットメールは送信されません。パスワードを忘れた場合は、ボリューム内の認証ファイルを削除して再起動し、再びデフォルトの認証情報でサインインします。
docker stop harnessrouter
docker run --rm -v harnessrouter:/data alpine rm -f /data/selfhost-auth.json
docker start harnessrouterHR_AUTH_DISABLED=1を指定すると、サインインによる保護が完全に無効になります。README では「他のユーザーが到達できないマシン」に限定しています。パブリック IP アドレスを持つ VPS はその条件に該当しないため、ノート PC で実行する場合を除き、保護を有効にしてください。
バージョンを確認してください。古いバージョンには認証ゲートがありません
ここは軽視できません。0.1.x と 0.2.0 のバージョンには認証ゲートがまったくありません。ポート 3000 に到達できるユーザーは、すでにコンソールへ入れる状態でした。0.3.0 がログイン機能を備えた最初のリリースです。これらの古いタグは現在も公開され、pull できます。そのため、古いタグを固定していたり、同僚からコピーした compose ファイルを使っていたりすると、認証ゲートのないコンソールを現在でも公開ポートで稼働させる可能性があります。
2026 年 8 月 19 日時点で、公開されている最新タグは 0.5.5 です。日付は 2026 年 8 月 18 日で、latest はこのタグを指しています。現在の設定を確認し、Docker Hub のタグ一覧と比較してください。
docker image ls harnessrouter/harnessrouter0.3.0 未満のバージョンは、後で対応するのではなく、直ちに置き換えてください。0.3.0 以上のバージョンでも、パスワードの変更が必要です。ポート 3000 をスキャンしている相手にとって、デフォルトパスワードが設定されていることと、パスワードがないことは同じだからです。このページに記載されたバージョン番号を最新情報として扱わないでください。これらは冒頭の日付時点で正しかったものであり、このプロジェクトは短い間隔でリリースされます。
プロバイダーを接続する
モデルプロバイダーを接続するまで、何も実行されません。コンソールの Integrations ページから追加するか、環境変数で docker run に渡します。値は JSON なので、シェルでは引用符で囲みます。
-e HR_SECRET_GLOBAL_HARNESS_CONN_ANTHROPIC='{"name":"anthropic","provider":"anthropic","api_key":"sk-ant-…"}'.env.example には、プロバイダーファミリーごとに接続変数を指定します。claude-code バックエンドには HR_SECRET_GLOBAL_HARNESS_CONN_ANTHROPIC、codex バックエンドには HR_SECRET_GLOBAL_HARNESS_CONN_OPENAI、OpenAI 互換エンドポイントには HR_SECRET_GLOBAL_HARNESS_CONN_CUSTOM を使用します。最後の接続先には、アグリゲーターや独自の推論サーバーを指定できます。対応する HR_SECRET_GLOBAL_HARNESS_POLICY_CLAUDE、HR_SECRET_GLOBAL_HARNESS_POLICY_CODEX、HR_SECRET_GLOBAL_HARNESS_POLICY_HERMES 変数では、各バックエンドがデフォルトで使用する接続を指定します。HR_SECRET_KEY は別の設定で、データベースをエージェントに接続する場合にのみ必要です。
HR_BACKENDS では、HR_BACKENDS=claude,codex,hermes のように読み込むバックエンドを選択します。事前に知っておくべき既知の問題があります。hermes を省略した値を指定すると、コンテナはエラーメッセージを表示せず、ステータス 1 ですぐに終了します。起動から 1 秒後に docker ps -a で Exited (1) が表示され、docker logs には有用な情報が何も表示されません。upstream で修正されるまで、一覧から hermes を削除しないでください。Hermes だけを使用する場合は、Hermes エージェントを独自の VPS で実行するほうが小規模な構成になります。
コンソールを使わずに API を呼び出す
コンソールは必須ではありません。コンソールと API は同じ API を使用し、Responses 形式のコントラクトに従います。まずサインインして、セッション Cookie を取得します。
curl -c hr.cookies http://localhost:3000/api/selfhost/login \
-H 'content-type: application/json' \
-d '{"username":"harnessrouter","password":"your-password"}'次に、metadata.harness_id でハーネスを指定し、接続済みのプロバイダーが実際に提供しているモデルを指定してタスクを送信します。
curl -s -b hr.cookies http://localhost:3000/api/harness/v1/responses \
-H 'content-type: application/json' \
-d '{"input":"Reply with exactly this and nothing else: it works.",
"metadata":{"harness_id":"codex"},
"model":"gpt-5.4-mini",
"stream":false}'出力ブロックとトークン数を含む JSON オブジェクトが返れば、ハーネスは実行されています。harness_id を codex から claude に変更すると、同じリクエストが別のハーネスに送信されます。この切り替えこそが、このソフトウェアの存在理由です。上記のカスタム接続を使うと、すでにホストしている OpenAI 互換エンドポイントをハーネスに指定できます。VPS 上の自己ホスト型 DeepSeek ハーネスも、この方法で接続されています。
ラップトップからポートを公開せずにアクセスする
方法は2つあり、どちらも0.0.0.0のポートを直接公開しません。
SSH トンネルは最も手軽で、サーバーに何もインストールする必要がありません。手元のマシンのローカルポートから、VPS の loopback へ転送します。
ssh -N -L 3000:127.0.0.1:3000 you@your-vpsこの状態のままにして、ブラウザーでhttp://localhost:3000を開きます。SSH がbind: Address already in useを出力する場合、ラップトップ上ですでに別のプロセスがポート 3000 を使用しています。その場合は-L 3100:127.0.0.1:3000で別のローカルポートを選び、ポート 3100 にアクセスします。
終端リバースプロキシは、他の人もアクセスする必要がある場合の方法です。プロキシで TLS (transport layer security) 証明書を管理し、loopback へ転送します。README には Caddy の設定例があります。
console.example.com {
encode zstd gzip
reverse_proxy 127.0.0.1:3000 {
flush_interval -1 # agent turns stream for minutes; never buffer them
}
}flush_interval -1は見落とされやすい行です。Agent は数分間にわたってストリームトークンを生成します。プロキシが応答をバッファリングすると、ターンが終わるまでトークンが保持されるため、コンソールが停止したように見え、その後すべてが一度に表示されます。Nginx では location ブロック内にproxy_buffering off;を指定します。どちらを選んでも、DNS 名はプロキシを指し、コンテナは loopback で待ち受けるようにします。リバースプロキシとしての Nginx、Caddy、Traefik の比較では、環境に適した製品を確認できます。
専用ユーザーで実行し、rootでは実行しない
Docker デーモンは root として実行されます。また、docker グループのメンバーになることは root 権限を持つことと同等です。メンバーはホストのファイルシステムをマウントするコンテナを起動できるためです。そのため、「チームを docker グループに追加する」ことは、プロバイダーの鍵を保管するホスト上で root 権限を配布することになります。
簡単な方法は、compose ファイルと .env の所有者となるサービスアカウントを作成し、それらのファイルを共有ホームディレクトリに置かないことです。
sudo adduser --disabled-password --gecos "" harness
sudo install -d -o harness -g harness -m 750 /srv/harnessrouterより強固な方法は rootless Docker です。この構成では、デーモン自体がその非特権ユーザーとして実行されます。ユーザーには newuidmap と newgidmap 用の uidmap パッケージが必要です。また、/etc/subuid と /etc/subgid に少なくとも 65536 個のサブ UID が必要です。
sudo apt install -y uidmap docker-ce-rootless-extras
sudo loginctl enable-linger harness
sudo -iu harness
dockerd-rootless-setuptool.sh install
export DOCKER_HOST=unix:///run/user/$(id -u)/docker.sock
systemctl --user enable --now dockerここでは loginctl enable-linger は必須です。これがないと、最後のセッションを閉じた時点でユーザーの systemd インスタンスが停止し、ログアウトするとコンテナも停止します。rootless が Security Options の下に表示されることを、docker info で確認します。rootless モードでは、追加設定なしに 1024 未満のポートをバインドできません。ただし、ポート 3000 はこの範囲より上なので、ここでは問題ありません。アカウント自体の設定については、VPS で最小権限のユーザーを作成するで説明しています。
何が問題になり、何を確認できるか
コンテナが起動から1秒後に終了し、ログも空です。 docker ps -a には Exited (1) と表示されます。これは上記の HR_BACKENDS の問題です。値から hermes が抜けています。追加してください。
初回起動が完了しません。 ログが installing 行の後で止まり、ready on :3000 が表示されません。イメージに agent CLI が含まれていないため、ホストからネットワークに接続して agent CLI を取得できません。外向きルートまたはプロキシ設定を修正してから、再起動してください。
コンソールは読み込まれますが、すべてのタスクが失敗します。 provider が接続されていません。イメージにはモデルが同梱されておらず、無料枠も含まれていません。そのため、新しいインスタンスでサインインできても、何も実行できません。
プロキシ経由でコンソールが回答の途中で固まります。 ターンが終了すると、出力が1つのブロックで表示されます。これはレスポンスのバッファリングです。Caddy では flush_interval -1 を、Nginx では proxy_buffering off; を設定してください。
トンネルは起動していますが、ラップトップから接続できません。 サーバーで docker port harnessrouter を実行してください。何も表示されない場合、コンテナは何も公開していません。-p なしで起動されたためです。
実行する価値はありますか?
複数の harness を実際に使っていて、それぞれ3つある endpoint と credential store を1つにまとめたい場合は、実行する価値があります。harness を書き換えずに設定値として扱う製品を構築する場合も、実行する価値があります。これが UHP で得られるものです。ただし、プロトコルがまだ新しい点には注意が必要です。
harness を1つしか使わない場合は、実行する価値はありません。サーバーにその CLI をインストールするほうが構成要素が少なく、ログインが間に入ることもありません。また、複数の harness の前に1つの API を置くのではなく、複数の agent が1つのタスクで協調する構成を求めている場合にも適していません。そのパターンには別のツールが必要です。Omnigent などの multi-agent harnessを参照してください。いずれの場合も、デプロイのルールは変わりません。loopback に bind し、password を変更し、0.3.0 以上の tag を固定し、専用の user を使用してください。
FAQ
HarnessRouter をポート 3000 で公開しても安全ですか?
いいえ。コンソールは harness の作成、すべての transcript の読み取り、shell と filesystem にアクセスできる agent の実行を行います。また、接続した provider の key も保持します。そのため、ポートを公開するとこれらすべてが外部にさらされます。-p 127.0.0.1:3000:3000 を使用して loopback で公開し、SSH tunnel または TLS 終端を担う reverse proxy 経由でアクセスしてください。host firewall だけでは不十分です。Docker は独自のルールを kernel の nat table に書き込むため、ufw で拒否と表示されていても、公開ポートは internet からの接続に応答します。sudo ss -ltnp | grep 3000 で確認してください。127.0.0.1:3000 と表示されるはずです。
ログインゲートが追加された HarnessRouter のバージョンはどれですか?
0.3.0 です。0.1.x と 0.2.0 には認証機能がまったくなく、現在も両方の tag が公開され、pull できます。そのため、これらを実行している場合は、誰もポートを見つけないことに頼っている状態です。19 August 2026 時点の最新 tag は 0.5.5 で、日付は 18 August 2026 です。docker image ls harnessrouter/harnessrouter を実行して使用中のバージョンを確認し、このページではなく Docker Hub の tag 一覧と比較してください。現在のバージョンでも default password は変更してください。
HR_BACKENDS を設定すると、なぜ container がすぐ終了するのですか?
hermes を含まない HR_BACKENDS の値を指定すると、既知の問題として container は status 1 で直ちに終了し、エラーメッセージも表示されません。この症状では、1、2 秒以内に docker ps -a に Exited (1) が表示され、docker logs には有用な情報が何も出ません。upstream で修正されるまで、HR_BACKENDS=claude,codex,hermes のように hermes を list に残してください。
HarnessRouter の初回起動には internet access が必要ですか?
はい。agent CLI は image に同梱されず、初回起動時に取得されます。各 CLI が独自の licence を持つためです。outbound route のない host では installing の行が表示され、その後 ready on :3000 に到達しません。download は volume ごとに 1 回だけ行われます。そのため、2 回目以降の起動には数秒しかかからず、接続した model provider 以外への network access も必要ありません。
コンソールの password を失いました。どうすれば再びログインできますか?
account system も mail server もないため、reset email はありません。container を停止し、volume から /data/selfhost-auth.json を削除してから、再度起動してください。その後、default credentials で sign in し、Profile page から新しい password を設定します。container と volume の名前がどちらも harnessrouter の場合は、docker stop harnessrouter、docker run --rm -v harnessrouter:/data alpine rm -f /data/selfhost-auth.json、docker start harnessrouter の順に実行します。