Hermes Agent 三合一 Docker 部署指南
这是什么
Hermes Agent 是一个 AI 代理框架,官方推荐用三个容器分别跑网关、监控面板和聊天界面。这篇文章记录我怎么把它们跑起来的,踩了哪些坑,以及最终的配置方案。
为什么需要三个容器
简单说就是职责分离:
- hermes — 网关 API,处理所有请求,端口 8642
- dashboard — 监控面板,看运行状态和会话,端口 9119
- webui — 浏览器聊天界面,端口 8787
三个容器共享同一份数据(配置、会话、记忆、技能),靠挂载同一个目录实现。
踩坑记录
坑 1:端**露
默认端口直接映射到 0.0.0.0,意味着外网可以直接访问。生产环境必须加防火墙或者反向代理。
坑 2:权限问题
三个容器默认用户可能不一样,导致同一个挂载目录互相读写报 Permission denied。解决方法是给三个容器统一指定 user: "1000:1000",同时把宿主机目录 owner 设为 1000:1000,权限 755。
坑 3:WebUI 需要 agent 源码
WebUI 启动时会检查 agent 源码目录,没有的话功能受限。源码在 agent 容器的 /opt/hermes 里,需要复制到宿主机再挂载给 webui。
坑 4:webui 的 HERMES_HOME
agent 和 dashboard 设置了 HERMES_HOME=/opt/data,但 webui 没有设置这个变量,导致它读不到 config.yaml,报 "No LLM provider configured"。加一行 HERMES_HOME=/home/hermeswebui/.hermes 就解决了。
坑 5:配置变更不会自动生效
在 agent 里通过 hermes setup 配置了模型,webui 不会自动感知,需要重启 webui 容器。
最终配置
目录结构
project/
**── docker-compose.yml
**── init.sh # 从镜像复制 agent 源码,只需跑一次
**── hermes/
**── data/ # 配置、会话、记忆、skills
**── hermes/ # agent 源码(init.sh 自动复制)
docker-compose.yml
services:
hermes:
image: nousresearch/hermes-agent:latest
container_name: hermes
restart: unless-stopped
command: gateway run
user: "1000:1000"
ports:
- "8642:8642"
volumes:
- ./hermes/data:/opt/data
environment:
# - GATEWAY_ALLOW_ALL_USERS=true #(实测关闭没有问题)
- TZ=Asia/Shanghai
networks:
- hermes-net
dashboard:
image: nousresearch/hermes-agent:latest
container_name: hermes-dashboard
restart: unless-stopped
command: dashboard --host 0.0.0.0 --insecure
user: "1000:1000"
ports:
- "9119:9119"
volumes:
- ./hermes/data:/opt/data
environment:
- GATEWAY_HEALTH_URL=http://hermes:8642
- TZ=Asia/Shanghai
networks:
- hermes-net
depends_on:
- hermes
webui:
image: ghcr.io/nesquena/hermes-webui:latest
container_name: hermes-webui
restart: unless-stopped
ports:
- "8787:8787"
volumes:
- ./hermes/data:/home/hermeswebui/.hermes
- ./hermes/hermes:/home/hermeswebui/.hermes/hermes-agent
- ./hermes/data/workspace:/workspace
environment:
- HERMES_HOME=/home/hermeswebui/.hermes
- HERMES_WEBUI_HOST=0.0.0.0
- HERMES_WEBUI_PORT=8787
- HERMES_WEBUI_STATE_DIR=/home/hermeswebui/.hermes/webui
- TZ=Asia/Shanghai
- WANTED_UID=1000
- WANTED_GID=1000
# - HERMES_WEBUI_PASSWORD=your-password
networks:
- hermes-net
depends_on:
- hermes
networks:
hermes-net:
driver: bridge
init.sh
#!/bin/bash
set -e
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
DEST="$SCRIPT_DIR/hermes/hermes"
DATA="$SCRIPT_DIR/hermes/data"
IMAGE="nousresearch/hermes-agent:latest"
mkdir -p "$DATA"
mkdir -p "$DEST"
# 统一 UID 1000:1000,三个容器都用同一个用户
sudo chown -R 1000:1000 "$DATA"
sudo chmod -R 755 "$DATA"
if [ -f "$DEST/pyproject.toml" ]; then
echo "Source already exists at $DEST"
else
echo "Copying agent source from $IMAGE ..."
docker run --rm \
--entrypoint sh \
-v "$DEST:/dest" \
"$IMAGE" \
-c 'cd /opt/hermes && tar \
--exclude=.venv \
--exclude=.playwright \
--exclude=node_modules \
--exclude=package.json \
--exclude=package-lock.json \
--exclude=__pycache__ \
--exclude=hermes_agent.egg-info \
--exclude=.git \
--exclude=tests \
--exclude=docs \
--exclude=website \
-cf - . | (cd /dest && tar xf -)'
echo "Done. Source copied to $DEST"
fi
sudo chown -R 1000:1000 "$DEST"
sudo chmod -R 755 "$DEST"
echo ""
echo "Ready. Run: docker compose up -d"
部署步骤
chmod +x init.sh
./init.sh
docker compose up -d
三条命令,完事。
访问地址:
- http://localhost:8642 — Gateway API
- http://localhost:9119 — Dashboard
- http://localhost:8787 — WebUI
配置模型
docker exec hermes hermes setup
按提示选 provider,填 API key。配完后重启 webui:
docker compose restart webui
可能遇到的问题
1. init.sh 报 Permission denied
需要 sudo 权限。如果不想用 sudo,手动 chown 也行。
2. webui 报 "No LLM provider configured"
确认 config.yaml 里有 providers 配置,然后 docker compose restart webui。
3. skills 同步报 "File exists"
无害。两个容器都会往 skills 目录写入,第二次启动时文件已存在就跳过了。
4. 上游镜像更新了
拉最新镜像重新启动:
docker compose pull
docker compose up -d
源码如果也需要更新,删掉 ./hermes/hermes/ 然后重新跑 init.sh。
5. 迁移
整个目录打包带走:
tar czf hermes.tar.gz hermes/ docker-compose.yml init.sh
目标机器解压后 docker compose up -d 就行。
为什么不用命名卷
命名卷迁移时需要 docker volume 操作,不直观。绑定挂载就是普通目录,打包复制就行,对运维更友好。代价是需要手动处理权限,但 init.sh 已经帮你搞定了。
已知问题
| 问题 |
风险 |
| 端**露 0.0.0.0 |
外网直接访问,无防火墙(自行修改配置) |
| 无 webui 密码 |
任何人能进聊天界面(自行修改配置) |
| SQLite 并发写 |
两个容器同时写 state.db 可能损坏 |
| skills 目录冲突 |
两个容器都往 skills 写入 |
| 无健康检查 |
依赖服务没就绪就启动 |
| 源码只复制一次 |
镜像更新后代码过时(删除源码目录重新初始化) |