SSD Nodes Learn 🎉 VPS $5.50/月起
指南 Matt Connor作者: Matt Connor · 更新于 2026-08-21

HarnessRouter自托管:统一Codex、Claude Code和Hermes API

用Docker自托管HarnessRouter,将Codex、Claude Code和Hermes接入一个API。本文详解部署命令、回环绑定、必须修改的默认登录密码,以及TLS访问配置。

HarnessRouter 会移除什么

您可以在自己管理的服务器上自行托管 HarnessRouter Community Edition,在多个 agent harness 前统一接入一个 API。agent harness 是一个循环驱动模型的命令行程序:它会保持会话、编辑文件、运行命令,并将进度流式返回给发起任务的程序。Codex、Claude Code 和 Hermes 都属于此类工具,但它们各自有独立的安装方式、凭据格式和会话定义。HarnessRouter 将它们全部运行在同一个容器中,并在前面提供一个 HTTP 端点、一个登录入口和一个统一的密钥存储。

这就是它的全部设计理念,但必须明确说明相应代价。您需要向服务器添加一个容器、一个登录入口、一个卷和一套升级流程,才能将多个组件整合为一个整体。如果您目前只运行一个 harness,直接安装该 harness 会比这种方案更简单。这项取舍会在本文最后一节说明,因此请先阅读该节,再进行部署。

以下内容均已根据镜像标签 0.5.5 进行验证,该镜像于 19 August 2026 拉取。项目大多数日期都会发布新标签,因此请检查您实际运行的标签,不要在一个月后仍直接依赖本文。相关命令来自项目 README:github.com/HarnessRouter/harnessrouter

Unified Harness Protocol 的实际含义

HarnessRouter 实现了 Unified Harness Protocol(UHP),该协议发布于 unifiedharnessprotocol.org。UHP 规定了产品如何在 harness 上启动任务、跟踪运行中的任务、管理会话和文件,以及报告失败。该规范按日期进行版本管理。截至 19 August 2026,当前版本的日期为 2026-08-11。该网站将其称为一项草案标准,即“足够稳定,可以据此构建,并通过版本管理确保能够安全变更”。

请仔细理解这里的“开放标准”。同一家公司负责编写规范、开发参考实现,以及维护用于判定兼容性的 52-check 一致性测试套件。这对于如此早期的协议很常见,而 Apache-2.0 许可证意味着您可以派生其中任何部分。这也意味着 UHP 目前还不是多厂商标准。应将其视为一项新兴协议:它有实际用途,仍在持续变化,并且您自己的代码应能够在不重写的情况下停止使用它。

开始前的准备工作

需要安装 Docker,并预留约 4 GB 可用磁盘空间。您还需要一个已付费模型提供商的 API key。镜像拉取量约为 700 MB,其余磁盘空间用于 agent CLI 及其写入的工作区。镜像中不包含模型,也不提供试用 key,因此在连接模型提供商之前,任务会失败。HarnessRouter 本身采用 Apache-2.0 许可证。agent CLI 不受该许可证覆盖,因此不会随镜像发布,而是在首次启动时下载。

使用一条 docker run 自托管 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。每个卷只需执行一次安装,因此后续每次启动只需几秒,并且完全不会输出安装行。

这个下载会带来两个事实,在 VPS 上都很重要。第一,首次启动需要出站网络访问。该镜像不是自包含的,因此如果主机受出站流量过滤限制,或没有出站路由,就会卡在这里,并且永远不会输出 ready on :3000。它会在首次启动时失败,而不是在 docker pull 时失败;等到这里才发现问题会很难排查。第二,您正在依据第三方条款安装第三方软件。Claude Code 受 Anthropic 的条款约束,Hermes 则受其上游项目规定的条款约束,因此在商业使用前请检查两者的条款。

-v harnessrouter:/data 会创建一个命名 Docker 卷。所有持久化数据都位于 /data 中:SQLite 数据库、已存储文件、密钥存储和代理工作区。删除该卷就等于删除整个实例,其中包括 provider 密钥和所有会话记录。请在容器停止后备份,因为在 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 harnessrouter

Compose 变体,以及必须修改的行

仓库中提供了一个 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 命令速查表,其中介绍了日常使用的命令。

为什么端口发布到 127.0.0.1,而不是 0.0.0.0

-p 3000:3000 会将端口发布到主机的所有网络接口。-p 127.0.0.1:3000:3000 只会将端口发布到回环接口,因此只能从 VPS 本机访问。容器内部始终监听 3000,因此需要修改的是左侧的值。检查实际配置:

docker port harnessrouter
sudo ss -ltnp | grep 3000

ss 输出 127.0.0.1:3000 是正确的。0.0.0.0:3000 表示控制台位于公网。对于这里的场景,这比大多数自托管应用更危险,因为该控制台会创建 harness、读取所有会话记录、运行代理,并向这些代理提供 shell 和其工作区中的真实文件系统。它还保存了你连接的提供商密钥。任何能够访问未受保护控制台的人,都可以读取你的工作、运行命令,并消耗你的密钥。

主机防火墙无法解决这个问题。Docker 会将自己的规则写入内核的 nat 表,这些规则会在 ufw 管理的链之前进行评估。因此,即使 sudo ufw status 将已发布端口列为拒绝,该端口仍然可以访问。请从另一台机器测试,而不是从 VPS 本机测试,否则测试没有意义。这与在 3080 端口上无头运行 dsh说明的是同一个问题:先将服务绑定到回环接口,再有意决定访问它的方式。

在其他操作之前,先更改默认登录凭据

使用用户名 harnessrouter 和密码 harnessrouter 登录 http://localhost:3000。README 中会列出这些凭据,因为它们是占位符而不是机密。容器在每次启动时都会提醒您更改它们,直到您完成更改:

using the DEFAULT password. Set HR_AUTH_PASSWORD, or change it from the profile page, before exposing this instance.

您可以在 Profile 页面中更改密码,也可以在启动时设置密码,以便用于脚本化部署。HR_AUTH_USERHR_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 harnessrouter

HR_AUTH_DISABLED=1 会完全移除登录保护。README 将此选项限定为“其他人无法访问的设备”。具有公网 IP 地址的 VPS 不属于这种设备,因此除非您在笔记本电脑上运行此服务,否则请保持登录保护启用。

检查版本,因为旧版本没有身份验证门槛

这一点必须认真对待。0.1.x0.2.0 版本完全没有身份验证门槛:任何能访问 3000 端口的人都已经进入控制台。0.3.0 是首个支持登录的版本。这些旧标签仍在发布,也仍可拉取。因此,旧的固定标签,或从同事那里复制来的 compose 文件,今天仍可能让未设防的控制台暴露在公网端口上。

截至 2026 年 8 月 19 日,最新发布的标签是 0.5.5,发布日期为 2026 年 8 月 18 日,latest 指向该标签。先检查当前使用的版本,再与 Docker Hub 上的标签列表进行比较:

docker image ls harnessrouter/harnessrouter

低于 0.3.0 的任何版本都应立即替换,不要安排到以后处理。等于或高于该版本的版本仍需要修改密码,因为对于扫描 3000 端口的人来说,默认密码和没有密码没有区别。不要将本页面中的版本号视为当前版本。它们只在页面顶部所示日期有效,而该项目发布新版本的速度很快。

连接提供商

在连接模型提供商之前,不会运行任何内容。在控制台的 Integrations 页面中添加提供商,或通过环境变量将其传递给 docker run。该值为 JSON,因此请在 shell 中为其加引号:

-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_CLAUDEHR_SECRET_GLOBAL_HARNESS_POLICY_CODEXHR_SECRET_GLOBAL_HARNESS_POLICY_HERMES 变量用于指定每个后端默认使用的连接。HR_SECRET_KEY 是独立配置项,仅当您将数据库连接到 agent 时才需要。

HR_BACKENDS 用于选择要加载的后端,如 HR_BACKENDS=claude,codex,hermes 所示。在遇到一个已知问题前,请先了解这一点:任何缺少 hermes 的值都会使容器立即以状态码 1 退出,且不显示错误消息。启动后约一秒,您会在 docker ps -a 中看到 Exited (1),而 docker logs 不会显示有用信息。在上游修复此问题前,请将 hermes 保留在列表中。如果您只需要 Hermes 这一个 harness,在自己的 VPS 上单独运行 Hermes agent 是更简单的部署方式。

不使用控制台调用 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 中指定 harness,并指定已连接的提供商实际提供的模型:

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 已运行。将 harness_idcodex 改为 claude,即可将同一个请求发送到另一个 harness;这正是该软件存在的全部原因。上文的自定义连接用于将 harness 指向你已托管的 OpenAI 兼容端点,就像 在 VPS 上自托管的 DeepSeek harness 的接入方式一样。

无需开放端口即可从笔记本访问

有两种方法,而且都不会在 0.0.0.0 上直接开放端口。

SSH 隧道成本最低,也无需在服务器上安装任何软件。它会将本机上的一个本地端口转发到 VPS 的回环地址。

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 的反向代理适用于其他人也需要访问的情况。代理负责保存 TLS(传输层安全)证书,并将请求转发到回环地址。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 名称指向代理,并让容器只监听回环地址。比较 Nginx、Caddy 和 Traefik 作为反向代理的适用性介绍了如何根据服务器选择合适的方案。

使用独立用户运行,不要使用 root

Docker daemon 以 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,让 daemon 本身以该非特权用户身份运行。它需要 newuidmapnewgidmap 所需的 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 实例会在最后一个会话关闭时停止,因此用户注销后容器也会停止。使用 docker info 确认结果;该命令会在 Security Options 下列出 rootless。rootless 模式无法绑定 1024 以下的端口,除非进行额外配置。但这里无需配置,因为端口 3000 高于该限制。账户本身的创建方法请参阅在 VPS 上创建最小权限用户

出现故障时的表现

容器启动后 1 秒就退出,日志为空。 docker ps -a 显示 Exited (1)。这就是上文所述的 HR_BACKENDS 问题:您的值遗漏了 hermes。将其补回。

首次启动始终无法完成。 日志在一行 installing 后停止,且不会出现 ready on :3000。该主机无法访问网络来获取 agent CLI,因为这些 CLI 不在镜像中。修复出站路由或代理设置,然后重新启动。

控制台可以加载,但每个任务都失败。 没有连接任何 provider。镜像中没有捆绑模型,也没有免费层,因此新实例即使可以完成登录,也无法运行任何任务。

通过代理访问时,控制台在回答中途冻结。 回答结束后,输出才会一次性出现。这是响应缓冲导致的。请在 Caddy 中设置 flush_interval -1,或在 Nginx 中设置 proxy_buffering off;

隧道已建立,但笔记本电脑无法访问服务。 在服务器上运行 docker port harnessrouter。如果没有输出,说明容器没有发布任何端口,因为启动容器时未使用 -p

值得运行吗?

如果您确实使用多个 harness,并且希望使用一个端点和一个凭据存储,而不是分别维护三个端点和三个凭据存储,那么值得运行。如果您正在其上构建产品,并且希望将 harness 设为配置项,而不是在更换 harness 时重写代码,也值得运行。这正是 UHP 带来的价值,但请注意上文提到的协议尚不成熟。

如果您只使用一个 harness,则不值得运行。直接在服务器上安装该 CLI,组件更少,也不会有登录过程阻隔您与它之间的连接。如果您的需求是让多个代理协作完成一项任务,而不是通过一个 API 接入多个 harness,那么它同样不适合;这是另一类工具。此模式请参阅 多代理 harness(例如 Omnigent)。无论选择哪种方式,部署规则都不变:绑定到 loopback、更改密码、使用 0.3.0 或更高版本的固定 tag,以及使用独立用户。

FAQ

发布 HarnessRouter 的 3000 端口安全吗?

不安全。控制台可以创建 harness、读取所有 transcript、运行具有 shell 和文件系统访问权限的 agent,还会保存您连接的 provider key,因此开放端口会暴露所有这些功能。使用 -p 127.0.0.1:3000:3000 让服务仅监听 loopback,再通过 SSH 隧道或终止 TLS 的反向代理访问。仅依靠主机防火墙并不足够:Docker 会将自己的规则写入内核的 nat 表,因此即使 ufw 显示端口被拒绝,已发布的端口仍会响应来自互联网的请求。使用 sudo ss -ltnp | grep 3000 验证,输出应为 127.0.0.1:3000

哪个 HarnessRouter 版本加入了登录门禁?

0.3.00.1.x0.2.0 版本完全没有身份验证,而且这两个标签仍已发布并且仍可拉取,因此运行这些版本的用户实际上是在依赖没有人发现该端口。截至 19 August 2026,最新标签是 0.5.5,日期为 18 August 2026。运行 docker image ls harnessrouter/harnessrouter 查看当前使用的版本,并将其与 Docker Hub 上的标签列表进行比较,不要与本页面比较。即使使用当前版本,也应修改默认密码。

为什么设置 HR_BACKENDS 后容器立即退出?

任何省略 hermesHR_BACKENDS 值都会使容器立即以状态码 1 退出,且不显示错误消息。这是项目 README 中记录的已知问题。其表现为一两秒内在 docker ps -a 中出现 Exited (1),而 docker logs 中没有有用信息。在上游修复该问题前,请像 HR_BACKENDS=claude,codex,hermes 那样将 hermes 保留在列表中。

HarnessRouter 首次启动需要访问互联网吗?

需要。agent CLI 会在首次启动时下载,而不是随镜像一起发布,因为每个 CLI 都有自己的许可证。没有出站路由的主机会输出 installing 行,然后无法到达 ready on :3000。每个卷只需下载一次,因此后续启动只需几秒,除连接的模型 provider 外不需要网络访问。

我忘记了控制台密码。如何重新登录?

没有密码重置邮件,因为该系统没有帐户系统,也没有邮件服务器。停止容器,从卷中删除 /data/selfhost-auth.json,然后重新启动容器。接着使用默认凭据登录,并在 Profile 页面设置新密码。如果容器和卷都命名为 harnessrouter,依次执行 docker stop harnessrouterdocker run --rm -v harnessrouter:/data alpine rm -f /data/selfhost-auth.jsondocker start harnessrouter