第十六篇:Docker 部署 Hermes——干净又省心
Hermes Agent 官方教程第十六篇:用 Docker 容器运行 Hermes,环境隔离、开箱即用。
第十六篇:Docker 部署 Hermes——干净又省心
各位小伙伴,欢迎回来!今天咱们聊点省心的事儿——用 Docker 跑 Hermes。为啥说省心?因为镜像本身是无状态的,你的所有配置、API 密钥、会话、技能、记忆,全都存在宿主机挂载的一个目录里。想升级?拉个新镜像重新跑一下,配置一点不丢,干净利落。
镜像标签:latest、stable、main 怎么选
拉镜像之前,先搞清楚几个标签的含义:
| 镜像标签 | 含义 |
|---|---|
latest / stable |
通过了稳定版发布门禁的镜像。 |
main |
从 main 分支构建发布的开发版镜像。 |
X.Y.Z |
带版本号的稳定镜像。想要精确锁定部署,请用 digest。 |
构建流程会同时构建并测试 amd64 和 arm64 镜像,稳定版发布直接使用测试过的镜像归档,而不是重新构建。只有完整的 release 晋级流程才会移动 stable 和 latest 标签,往 main 分支推代码不会推进这两个标签。
镜像里的 Python 环境跟随 pyproject.toml 和 uv.lock(目前是 Python 3.14),它精选的 extras 并不是原生桌面打包用的 --all-extras 那一套,也不包含 Electron 桌面应用。
首次运行:初始化配置
第一次跑 Hermes,先建个数据目录,然后交互式启动容器,跑一遍设置向导:
mkdir -p ~/.hermes
docker run -it --rm \
-v ~/.hermes:/opt/data \
nousresearch/hermes-agent setup
向导会问你 API 密钥,然后写进 ~/.hermes/.env。这一步只需做一次。强烈建议顺手把聊天平台(Telegram、Discord 等)配好,后面网关才能干活。
小贴士:在容器里跑一次
hermes setup --portal,刷新令牌会持久化到挂载的~/.hermes卷里,之后就不用重复登录了。
网关模式:后台常驻
配置好之后,用守护模式把网关跑起来:
docker run -d \
--name hermes \
--restart unless-stopped \
-v ~/.hermes:/opt/data \
-p 8642:8642 \
nousresearch/hermes-agent gateway run
8642 端口是网关的 OpenAI 兼容 API 服务和健康检查端点。只用聊天平台的话,这个端口可以不映射;但想要仪表盘或外部工具访问网关,就必须暴露它。
TUI 也用这个受监管的网关作为本地后端,即使没配任何聊天平台也一样:交互式 --tui 启动会自动拉起已注册的 s6 网关并等它就绪。普通命名 profile 会挂到服务它们的根网关上;显式配置了 gateway.standalone: true 的 profile 才用自己的注册槽位。客户端启动不会安装服务,也不会绕开 s6 跑一个不受监管的进程。
交互式容器(带终端输入输出,比如 docker run -it ... --tui 或 bash)里,前台命令占着终端,entrypoint 默认设了 S6_LOGGING=2 和 S6_CMD_USE_TERMINAL=1,受监管的网关和仪表盘输出不会覆盖你的屏幕,而是写进容器内 s6 轮转的兜底日志 /run/uncaught-logs/current。这个目录是运行时存储,容器重启或删除后就没了;各 profile 的网关文件日志和普通 Hermes 日志仍在上面那些路径里。无头容器则继续把服务输出转发到 docker logs,显式设置的 S6_LOGGING 和 S6_CMD_USE_TERMINAL 优先于这些交互式默认值。
新变化:s6 自动守护
官方镜像里有个大变化——gateway run 现在由 s6-overlay 自动监管:网关进程崩了,几秒内自动重启,容器本身不会退出。docker logs 里能看到监管输出。想退出这种监管模式?加 --no-supervise 或设 HERMES_GATEWAY_NO_SUPERVISE=1,适合 CI 测试那种希望容器跟着网关一起退出的场景。生产环境建议保持默认。
想确认监管到底有没有生效?在容器里跑 hermes status --full,会看到一行 Manager: s6 (container supervisor),说明网关正被 s6 托管着。想看更原始的监管视图,可以用 /command/s6-svstat /run/service/gateway-<name> 查单个服务的状态。这里有个小坑要注意:/command/ 这个路径只对监管树里的进程在 PATH 上生效,你要是从宿主机用 docker exec 进去调,得写绝对路径,不然会提示找不到命令。
开启 API 服务器
默认 API 服务器是关的。想对外暴露,需要设 API_SERVER_ENABLED=true,同时把监听地址改成 0.0.0.0,再配个至少 8 位的密钥:
docker run -d \
--name hermes \
--restart unless-stopped \
-v ~/.hermes:/opt/data \
-p 8642:8642 \
-e API_SERVER_ENABLED=true \
-e API_SERVER_HOST=0.0.0.0 \
-e API_SERVER_KEY="$(openssl rand -hex 32)" \
-e API_SERVER_CORS_ORIGINS='*' \
nousresearch/hermes-agent gateway run
注意:给公网机器开端口有安全风险,不懂就别乱开。
仪表盘:一个容器全搞定
想要 Web 仪表盘?加个环境变量就行:
docker run -d \
--name hermes \
--restart unless-stopped \
-v ~/.hermes:/opt/data \
-p 8642:8642 \
-p 9119:9119 \
-e HERMES_DASHBOARD=1 \
nousresearch/hermes-agent gateway run
仪表盘同样由 s6 监管,崩了自动拉起,日志直接进 docker logs。
镜像里到底装了啥
官方镜像基于 Debian 13.4,里面预置了这些东西,省得你手动装:
- 一套 Python 3.14 环境,从提交进仓库的
uv.lock同步而来,之后以「无依赖可编辑安装」的方式装上 Hermes。 - 精选 extras:
all、messaging、otlp、anthropic、bedrock、azure-identity和matrix。注意这不是--all-extras。 - 来自 digest 锁定 Node 源镜像的 Node.js 26 和 npm。
- PM 锁定的 uv、完整版 Chromium、FFmpeg 和 ripgrep,都放在
/opt/hermes/tools。 - 系统自带的 Git、OpenSSH、Docker CLI 以及 Chromium 共享库。
- 预构建的 TUI / 仪表盘资源,以及烘焙好的 Photon sidecar 依赖。
- s6-overlay,负责进程监管和僵尸进程清理。
镜像把 /opt/hermes 当作运行时不可变的安装树。可选的 Python extras、Node 工作区和 TUI 资源必须在构建镜像时烘焙进去;运行时懒安装被禁用,这样受监管的网关和 docker exec hermes … 命令就不会试图把依赖产物写回只读源码树。
Chromium 是通过 PM 安装的,不是 npx playwright install;构建时会把解析出的可执行文件路径记录在 /etc/hermes/chromium-executable-path,Hermes 启动浏览器时会读取它。PLAYWRIGHT_BROWSERS_PATH 指向 /opt/hermes/tools,在数据挂载盘之外。每个镜像(包括不带 -desktop 后缀的)都带着完整的 Chromium 构建,而不是 Playwright 那个更轻的无头 shell:同一个锁定、校验过的浏览器同时服务无头浏览和有界面的 Bot Screen 会话,代价就是镜像体积更大。
挂载盘上的数据库:小心 WAL 踩坑
有个容易忽略的坑:会话存在 SQLite 数据库(/opt/data/state.db)里,默认用 WAL 日志模式。WAL 依赖共享内存,而跨虚拟机的挂载(macOS 上的 Docker Desktop、Podman、OrbStack,Windows 上的 Docker Desktop)不保证这一点,多个进程同时写可能悄悄把库写坏,主文件还照样能通过完整性检查。
Hermes 的处理方式(v2026.9.14 起):全新的库如果落在这种挂载上,会直接用回滚(DELETE)日志模式并打一条警告,你不用管;已经存在的 WAL 库不会被在线降级,而是启动时报错、hermes doctor 也会标出来。修法两种:停掉所有用到这个库的进程,用镜像自带的 Python 做一次离线转换(docker exec hermes python3 -c "import sqlite3; print(sqlite3.connect('/opt/data/state.db').execute('PRAGMA journal_mode=DELETE').fetchone()[0])"),并在 config.yaml 里设 database.journal_mode: delete;或者干脆把数据目录挪到原生卷上,比如用命名卷 -v hermes-data:/opt/data,它跑在虚拟机自己的 ext4 上,WAL 正常用。NFS、SMB、普通 FUSE 挂载检测不出来,那就手动设 database.journal_mode: delete。
无人值守网关的防呆设置
最后提醒一句:无人值守的网关和定时任务会话,现在默认就开启工具循环硬停止(通过 non_interactive_hard_stop_enabled),交互式的 CLI、TUI、桌面和 ACP 会话仍然只警告不硬停。想给某个无人值守部署关掉,在对应 profile 的 config.yaml 里写:
tool_loop_guardrails:
non_interactive_hard_stop_enabled: false
好了,Docker 部署就这些。下期见!
📖 官方文档
本文根据 Hermes Agent 官方文档编写,原文见:官方文档 › user-guide/docker