Docker 部署 Hermes Agent 实战:踩坑与最佳实践
本文基于 M5 MacBook Air 32G 上使用 Colima 运行 Docker 虚拟机的实战经验,介绍如何在 Docker 中部署 Hermes Agent,解决数据持久化、权限隔离、工具环境保留等关键问题,并分享踩坑记录和最佳实践。
为什么选择 Docker 部署
Hermes Agent 依赖 Python 虚拟环境、Node.js 运行时、Playwright 浏览器以及大量系统级工具(ffmpeg、pandoc、ripgrep 等)。直接裸机安装会导致:
- 依赖冲突:多个项目共用 Python/Node 版本时容易出错
- 难以迁移:换服务器需要重新配置所有环境
- 权限混乱:不同服务以不同用户运行,文件属主不一致
Docker 部署的核心优势:
| 优势 | 说明 |
|---|---|
| 环境隔离 | 所有依赖封装在镜像内,不污染宿主机 |
| 可复现 | 镜像 SHA256 校验,每次构建结果一致 |
| 数据分离 | 用户数据挂载在 /opt/data volume,与代码层完全解耦 |
| 权限可控 | 运行时以非 root 用户执行,最小化攻击面 |
核心架构设计
目录职责划分
Hermes Agent 的目录设计是整个部署方案的核心:
# 代码层(镜像内,只读)
/opt/hermes/ # 代码、venv、node_modules
/opt/hermes/.venv/ # Python 虚拟环境
/opt/hermes/bin/ # 可执行文件
# 数据层(Volume 挂载,持久化)
/opt/data/ # 用户数据、配置、sessions、skills
/opt/data/.local/bin # agent 安装的 npm/pip 工具
关键设计:容器内 $HOME 设为 /opt/data,npm prefix(~/.local)自然落在持久化路径内,工具不会因重启而丢失。
ENV HERMES_HOME=/opt/data
ENV PATH="/opt/hermes/bin:/opt/hermes/.venv/bin:/opt/data/.local/bin:${PATH}"
VOLUME [ "/opt/data" ]
进程监督:s6-overlay
镜像使用 s6-overlay 作为 PID 1,替代早期的 tini:
- 僵尸进程回收:s6-svscan 在 SIGCHLD 时非阻塞回收孤儿进程
- 服务监督:main-hermes、dashboard、per-profile gateway 均受监督,崩溃自动重启
- 启动顺序保证:cont-init.d 脚本按字典序执行,权限修复在服务启动前完成
非 root 运行
RUN useradd -u 10000 -m -d /opt/data hermes
每个监督服务通过 s6-setuidgid hermes 降权运行。docker exec 默认以 root 进入容器——为此提供了 /opt/hermes/bin/hermes shim,自动转发给 s6-setuidgid hermes 执行,避免写出 root 属主文件导致运行时 EACCES。
对官方 Dockerfile 的修改
1. 镜像源全面国内化
问题:官方镜像从 Docker Hub、deb.debian.org、npmjs.org、pypi.org 拉取,在国内构建速度极慢甚至超时。
解决方案:
# Node 基础镜像:官方 → DaoCloud 镜像
- FROM node:22-bookworm-slim AS node_source
+ FROM docker.m.daocloud.io/library/node:22-bookworm-slim AS node_source
# apt 源:deb.debian.org → 中科大镜像
+ RUN sed -i 's|deb.debian.org|mirrors.ustc.edu.cn|g' /etc/apt/sources.list.d/debian.sources
# npm registry → npmmirror
+ ENV npm_config_registry=https://registry.npmmirror.com # 比 npm config set 更早生效,构建阶段也生效
# PyPI → 清华镜像
+ RUN uv sync ... --index-url https://pypi.tuna.tsinghua.edu.cn/simple/
2. 新增系统工具包
问题:官方镜像为精简体积裁掉了部分工具,我们的使用场景需要它们。
+ wget # 部分脚本依赖 wget 而非 curl
+ file jq # 文件类型检测、JSON 处理
+ librsvg2-bin # SVG 转换(cairosvg 的系统依赖)
+ pandoc # 文档格式转换
+ g++ make cmake # v0.17.0 Matrix gateway 依赖的原生编译工具
+ fonts-noto-cjk fonts-noto-cjk-extra # 中文字体
3. 新增 Python 包
RUN uv pip install --no-cache-dir \
--index-url https://pypi.tuna.tsinghua.edu.cn/simple/ \
feedparser markdown whoosh \ # RSS 解析、Markdown 渲染、全文搜索
Pillow numpy jieba \ # 图像处理、数值计算、中文分词
duckduckgo-search websocket-client \ # 搜索、WebSocket 通信
cairosvg soundfile weasyprint \ # SVG/音频/HTML→PDF 转换
wordcloud tiktoken pyarrow \ # 词云、token 计数、列式存储
pytest litellm \ # 测试框架、多模型 LLM 接口
rtk-hermes # RTK Hermes 集成
4. rtk 固定版本(构建可复现)
ARG RTK_VERSION=0.42.4
RUN curl -fsSLk ... "https://github.com/rtk-ai/rtk/releases/download/v${RTK_VERSION}/rtk-..."
不固定版本会在每次 docker build 时拉取最新 release,导致不同时间构建的镜像行为差异。ARG 固定后可通过 --build-arg RTK_VERSION=x.y.z 按需升级。
5. s6-overlay 下载方式优化
问题:官方用 curl 在 RUN 步骤内下载 s6-overlay,每次构建都重新下载。
解决方案:改用 ADD 指令,BuildKit 会将 tarball 缓存在层中,仅 URL 变化时才重新拉取。
# 改为 ADD,利用 BuildKit 缓存
+ ADD https://github.com/.../s6-overlay-x86_64.tar.xz /tmp/s6-overlay-x86_64.tar.xz
+ ADD https://github.com/.../s6-overlay-aarch64.tar.xz /tmp/s6-overlay-aarch64.tar.xz
- curl -fsSL --retry 3 -o /tmp/s6-overlay-arch.tar.xz "..."
构建镜像
Dockerfile 修改完成后,先构建镜像再启动容器:
# 进入 Dockerfile 所在目录
cd /path/to/hermes-agent
# 构建镜像(首次约 10-15 分钟,后续有缓存会快很多)
docker build -t hermes-agent:v2026.6.19 .
# 验证镜像构建成功
docker images | grep hermes-agent
# 期望输出:hermes-agent v2026.6.19 <image_id> <size>
构建参数说明:
| 参数 | 说明 |
|---|---|
-t hermes-agent:v2026.6.19 |
镜像名称和标签,后续 docker run / compose.yml 中引用的就是这个名字 |
. |
构建上下文为当前目录(包含 Dockerfile) |
自定义 rtk 版本(可选):
docker build --build-arg RTK_VERSION=0.42.4 -t hermes-agent:v2026.6.19 .
国内构建加速提示:Dockerfile 已切换国内镜像源(apt/npm/PyPI),无需额外配置代理。如果 docker build 本身拉取基础镜像慢,可配置 Docker daemon 的 registry-mirrors。
部署步骤
1. 启动容器
方式一:docker run
docker run -d \
--name hermes \
--restart unless-stopped \
-p 127.0.0.1:9119:9119 \
-v ~/.hermes:/opt/data \
-e TZ=Asia/Shanghai \
-e HERMES_DASHBOARD=true \
-e HERMES_DASHBOARD_INSECURE=true \
hermes-agent:v2026.6.19 gateway run
方式二:docker compose(推荐)
compose.yml:
services:
hermes:
image: hermes-agent:v2026.6.19
container_name: hermes
restart: unless-stopped
ports:
- "127.0.0.1:9119:9119"
volumes:
- ~/.hermes:/opt/data
environment:
- HERMES_UID=${HERMES_UID:-10000}
- HERMES_GID=${HERMES_GID:-10000}
- TZ=Asia/Shanghai
- HERMES_DASHBOARD=true
- HERMES_DASHBOARD_INSECURE=true
command: ["gateway", "run"]
启动:
docker compose up -d
9119 端口绑定 127.0.0.1,仅本机访问。如需远程访问,通过 SSH 隧道或反向代理转发,不要直接绑定 0.0.0.0。
2. 验证运行状态
# 查看启动日志
docker logs hermes -f
# 查看 stage2-hook 初始化输出
docker logs hermes 2>&1 | grep '\[stage2\]'
# 进入容器调试(自动以 hermes 用户执行)
docker exec hermes hermes status
踩坑记录
坑 1:不要用 --user 启动容器
现象:docker run --user $(id -u):$(id -g) 启动后容器直接报错退出。
原因:s6-overlay 的 cont-init.d 阶段需要 root 权限执行 UID remap、chown 等操作。非 root 启动时这些步骤全部跳过,hermes 代码树(UID 10000)对任意其他 UID 均不可写,必然 EACCES 崩溃。
正确做法:以 root 启动(默认),通过 HERMES_UID / PUID 环境变量传入宿主机 UID。
坑 2:工具重启后消失(npm/pip 包丢失)
现象:在 Hermes 内安装了飞书 CLI、某个 npm 工具,重启容器后全部找不到。
原因:若未正确配置 $HOME,npm prefix 默认落在 ~/.local(容器内非持久路径),重启后镜像层重置,安装记录清空。
本项目的解法:HERMES_HOME=/opt/data 已将 $HOME 指向持久化 volume,~/.local 解析为 /opt/data/.local,PATH 也已包含 /opt/data/.local/bin。只要工具安装到 $HOME 路径下,重启后自动恢复。
坑 3:docker exec 写出 root 属主文件
现象:docker exec <container> hermes ... 执行后,/opt/data 下出现 root 属主文件,下次启动 gateway 报 PermissionError。
解法一(推荐):镜像内置了 /opt/hermes/bin/hermes shim,它检测到 root 调用时自动 s6-setuidgid hermes 降权,直接 docker exec hermes hermes ... 即可安全使用。
解法二:显式指定用户 docker exec -u hermes hermes ...。
坑 4:Docker 镜像拉取被拦截,可以切换国内镜像
现象:docker pull 超时或报 connection refused。
解法:Dockerfile 已切换到国内镜像源:
- Debian apt 源 →
mirrors.ustc.edu.cn - npm registry →
registry.npmmirror.com - PyPI →
pypi.tuna.tsinghua.edu.cn - Node 基础镜像 →
docker.m.daocloud.io/library/node:22-bookworm-slim
坑 5:UID 重映射后 .venv / ui-tui 权限错误
现象:指定 HERMES_UID 后,lazy install(discord.py、telegram 等适配器)失败,TUI 每次重新编译 dist/entry.js。
原因:usermod -u <new> hermes 会重新 chown $HOME(/opt/data),但不会动 /opt/hermes/.venv、/opt/hermes/ui-tui 等构建产物,它们仍属主 10000,新 UID 无法写入。
解法:stage2-hook.sh 在每次启动时检测 venv 属主,若与运行时 UID 不一致则执行一次 chown -R:
venv_owner=$(stat -c %u "$INSTALL_DIR/.venv")
if [ "$venv_owner" != "$actual_hermes_uid" ]; then
chown -R hermes:hermes "$INSTALL_DIR/.venv" "$INSTALL_DIR/ui-tui" ...
fi
安全注意事项
-
不要暴露 6789/6790 端口到公网:Web UI 和 Gateway 默认无认证,应通过反向代理(nginx/caddy)加 HTTPS + BasicAuth 后再对外。
-
API Key 保存在
/opt/data/.env:文件权限为600,仅 hermes 用户可读,不要将其纳入 git。 -
镜像完整性校验:s6-overlay tarball 在构建时通过 SHA256 校验,防止供应链攻击。
总结
Docker 部署 Hermes Agent 的核心要点:
- 目录分离:代码层只读,数据层持久化
- 权限管理:s6-overlay + hermes 用户降权
- 国内优化:镜像源全面切换到国内镜像
- 工具持久化:
HERMES_HOME=/opt/data确保 npm/pip 工具不丢失
按照本文档操作,应该能够顺利部署并运行 Hermes Agent。如有问题,欢迎在评论区交流。
更多推荐

所有评论(0)