欢迎关注我的CSDN:https://spike.blog.csdn.net/
免责声明:本文来源于个人知识与公开资料,仅用于学术交流,欢迎讨论,不支持转载。

在国内环境下构建并运行 Hermes Agent 与 WebUI 的单容器镜像:通过预拉取 python:3.12-slim 和 nousresearch/hermes-agent:latest、使用国内 APT/PyPI/uv 源、多阶段 Dockerfile 将 /opt/hermes Agent 源码内置到 WebUI 镜像中,降低构建时访问海外资源失败的风险;同时给出多实例运行、独立状态目录与工作区、飞书 OAuth 凭据按实例运行时注入、模型配置、健康检查、日志排查,以及 UID/GID、PyPI、Agent 源码、HTTP 登录 Cookie 等常见故障处理方案,整体目标是让同一个可复用镜像安全地支撑多个独立 Hermes-Expert 部署实例。
用于在国内网络环境中,构建和运行 Hermes Agent 与 WebUI 单镜像版本。目标是先逐个拉取构建所需镜像,再执行本地构建,减少 Docker build 过程中临时拉取镜像或访问海外 PyPI 源导致的失败。
- Hermes Agent:https://hermes-agent.nousresearch.com/docs/
- Hermes WebUI:https://github.com/nesquena/hermes-webui
1. 目标与适用范围
适用场景:
- 在国内服务器、办公网络或 CI 环境中构建 Hermes Agent 与 WebUI 镜像。
- 使用默认公开镜像名构建:
python:3.12-slim和nousresearch/hermes-agent:latest。 - 单容器运行 Hermes Agent 与 WebUI:WebUI 镜像内置 Agent 源码,启动后在同一容器环境中运行 Agent。
- 不使用双容器或三容器方案,不依赖运行时再拉取 Agent 源码镜像。
不包含:
- 多容器 Agent / WebUI / Dashboard 拆分部署。
- 真实账号、真实密钥、镜像仓库凭据或模型供应商凭据。
- 生产反向代理、TLS 证书申请和企业统一登录配置。
2. 数据源配置
镜像使用项目默认名称;APT 和 Python 依赖使用国内源加速。
| 类型 | 建议值 | 用途 |
|---|---|---|
| Python 基础镜像 | python:3.12-slim |
WebUI 镜像运行层基础 |
| Hermes Agent 基础镜像 | nousresearch/hermes-agent:latest |
提供 /opt/hermes Agent 源码 |
| WebUI 目标镜像 | hermes-expert-webui:<版本号> |
本地构建完成后的业务镜像 |
| Debian APT 源 | https://mirrors.tuna.tsinghua.edu.cn/debian |
构建阶段安装系统包 |
| Python / uv 源 | https://pypi.tuna.tsinghua.edu.cn/simple |
构建和启动阶段安装 Python 依赖 |
如果后续需要推送到企业 Harbor,可在构建完成后额外 docker tag 到内部仓库地址。主流程不假设任何私有仓库已经存在镜像。
配置分层原则:
- 构建期只放镜像来源、APT 源、Python 包源和版本号。
- 运行期才传入实例端口、状态目录、工作区、登录方式和 OAuth 配置。
FEISHU_APP_ID、FEISHU_APP_SECRET、密码、模型 API Key 等敏感值,只放在服务器本地的实例配置文件中,不写入 Dockerfile、镜像 tag、构建参数、文档示例或 Git 仓库。- 同一个镜像可以启动多个实例;每个实例可以配置不同的飞书应用。
3. 镜像预拉取顺序
逐个拉取默认基础镜像,确保失败点可以明确定位:
bash
docker pull python:3.12-slim
docker pull nousresearch/hermes-agent:latest
如果当前网络无法直接拉取默认镜像,先在可访问的环境中下载或通过企业代理同步,再把镜像导入当前机器。导入后仍建议保持默认 tag:
bash
docker load -i python-3.12-slim.tar
docker load -i hermes-agent-latest.tar
docker tag <python-image-id-or-source-tag> python:3.12-slim
docker tag <agent-image-id-or-source-tag> nousresearch/hermes-agent:latest
推荐在构建命令中仍显式写出 --build-arg HERMES_AGENT_IMAGE=nousresearch/hermes-agent:latest,让 Dockerfile 的 Agent 来源清晰可读。
注意,检查当前 Docker 的数据源,避免因数据源导致 Docker 无法下载。
bash
➜ ~ jq '.auths | keys' ~/.docker/config.json
[
"arcfox-prod-registry.cn-beijing.cr.aliyuncs.com",
"docker.1ms.run",
"https://index.docker.io/v1/",
"https://index.docker.io/v1/access-token",
"https://index.docker.io/v1/refresh-token"
]
➜ ~ docker logout arcfox-prod-registry.cn-beijing.cr.aliyuncs.com
Removing login credentials for arcfox-prod-registry.cn-beijing.cr.aliyuncs.com
➜ ~ docker logout docker.1ms.run
Removing login credentials for docker.1ms.run
➜ ~
4. Dockerfile 集成方案
hermes-webui 的 Dockerfile 只基于 python:3.12-slim 构建 WebUI,没有把 Hermes Agent 镜像作为构建阶段引入。单镜像集成需要增加 Agent 源码阶段,并把 Agent 镜像中的 /opt/hermes 复制进最终 WebUI 镜像。
文件:Dockerfile.hermes-expert-single-image
bash
ARG HERMES_AGENT_IMAGE=nousresearch/hermes-agent:latest
# 第一阶段只负责从指定 Hermes Agent 镜像中取出 /opt/hermes 源码。
# 最终镜像仍以 python:3.12-slim 为基础,避免继承 Agent 镜像中不需要的运行时内容。
FROM ${HERMES_AGENT_IMAGE} AS hermes-agent-source
FROM python:3.12-slim
LABEL maintainer="Hermes-Expert"
LABEL description="Hermes-Expert WebUI single-image build with embedded Hermes Agent"
# Hermes-Expert 单镜像面向内网/私有化部署,默认使用国内镜像源以提高构建稳定性。
# 如需官方源或企业代理,可在 docker build 时覆盖这些 build args。
ARG APT_MIRROR=https://mirrors.tuna.tsinghua.edu.cn/debian
ARG BUILD_APT_PROXY=
ARG PIP_INDEX_URL=https://pypi.tuna.tsinghua.edu.cn/simple
ARG UV_INDEX_URL=https://pypi.tuna.tsinghua.edu.cn/simple
ARG UV_DEFAULT_INDEX=https://pypi.tuna.tsinghua.edu.cn/simple
ARG HERMES_VERSION=unknown
ENV DEBIAN_FRONTEND=noninteractive \
PIP_INDEX_URL=${PIP_INDEX_URL} \
UV_INDEX_URL=${UV_INDEX_URL} \
UV_DEFAULT_INDEX=${UV_DEFAULT_INDEX} \
UV_CACHE_DIR=/uv_cache \
PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
PYTHONIOENCODING=utf-8
# 先切换 APT 源并安装基础工具。BUILD_APT_PROXY 仅在构建阶段写入临时配置,
# 本层末尾会删除代理文件,避免把构建环境细节带进运行时镜像。
RUN set -eux; \
if [ -n "${BUILD_APT_PROXY}" ]; then \
printf 'Acquire::http::Proxy "%s";\n' "${BUILD_APT_PROXY}" > /etc/apt/apt.conf.d/01proxy; \
fi; \
if [ -f /etc/apt/sources.list.d/debian.sources ]; then \
sed -i "s#http://deb.debian.org/debian#${APT_MIRROR}#g" /etc/apt/sources.list.d/debian.sources; \
sed -i "s#http://security.debian.org/debian-security#${APT_MIRROR}-security#g" /etc/apt/sources.list.d/debian.sources; \
fi; \
if [ -f /etc/apt/sources.list ]; then \
sed -i "s#http://deb.debian.org/debian#${APT_MIRROR}#g" /etc/apt/sources.list; \
sed -i "s#http://security.debian.org/debian-security#${APT_MIRROR}-security#g" /etc/apt/sources.list; \
fi; \
apt-get update -y; \
apt-get install -y --no-install-recommends \
apt-utils \
ca-certificates \
curl \
git \
gnupg \
locales \
openssh-client \
rsync \
wget \
xz-utils; \
apt-get upgrade -y; \
rm -rf /var/lib/apt/lists/* /etc/apt/apt.conf.d/01proxy; \
apt-get clean
# 生成 UTF-8 locale,确保中文路径、日志和前端文案在容器内按 UTF-8 处理。
# LC_ALL 保持为 C,延续上游镜像/脚本对稳定排序和命令输出的预期。
RUN localedef -i en_US -c -f UTF-8 -A /usr/share/locale/locale.alias en_US.UTF-8
ENV LANG=en_US.utf8
ENV LC_ALL=C
WORKDIR /apptoo
# 创建固定的非特权用户和常用挂载目录。入口脚本启动时会根据宿主挂载目录
# 自动对齐 UID/GID,然后再降权运行 WebUI;这里的 1024 是构建期默认占位。
# /app、/uv_cache、/workspace 使用 1777,方便运行时 UID 被重写后仍可写入。
RUN groupadd -g 1024 hermeswebui \
&& useradd -u 1024 -d /home/hermeswebui -g hermeswebui -G users -s /bin/bash -m hermeswebui \
&& mkdir -p /app /opt/hermes /uv_cache /workspace \
&& chown -R hermeswebui:hermeswebui /home/hermeswebui /app /uv_cache /workspace \
&& chmod 0755 /home/hermeswebui \
&& chmod 1777 /app /uv_cache /workspace
# 入口脚本负责一次性 root 初始化:对齐 UID/GID、准备挂载目录、转交环境变量,
# 然后切换到 hermeswebui 用户启动服务。镜像内不依赖 sudo。
COPY --chmod=555 docker_init.bash /hermeswebui_init.bash
# 把 Hermes Agent 源码嵌入最终镜像,让单容器部署无需额外挂载 agent source。
# 源码保持 root 拥有且全员只读,WebUI 只通过 PYTHONPATH/安装流程读取它。
COPY --from=hermes-agent-source --chown=root:root /opt/hermes /opt/hermes
RUN chmod -R a+rX /opt/hermes
# 供启动逻辑识别当前运行在容器内。
RUN touch /.within_container
# Install uv through the configured PyPI mirror so the build does not depend on
# a separate shell installer endpoint.
RUN python -m pip install --no-cache-dir --upgrade pip setuptools uv \
--index-url "${PIP_INDEX_URL}" \
--trusted-host pypi.tuna.tsinghua.edu.cn
# 复制当前 WebUI checkout。构建上下文可能包含本地虚拟环境或开发产物,
# 这些目录不应进入镜像,避免膨胀体积或覆盖容器内的运行时环境。
COPY --chown=root:root . /apptoo
RUN rm -rf /apptoo/venv /apptoo/.venv /apptoo/env /apptoo/.env /apptoo/node_modules
# 把构建时传入的版本号写入应用模块,设置页可直接显示镜像对应版本。
# 未传 HERMES_VERSION 时保留 unknown,避免本地构建依赖 git 元数据。
RUN echo "__version__ = '${HERMES_VERSION}'" > /apptoo/api/_version.py
# 单镜像默认在容器内同时提供 WebUI 和 Hermes Agent 源码:
# - WebUI 监听 0.0.0.0:8787,交给 Docker 端口映射决定对外暴露范围。
# - HERMES_WEBUI_AGENT_DIR/PYTHONPATH 指向内置 Agent 源码。
# - bundled skills 在首次挂载的 Hermes home 缺失技能时自动播种。
ENV HERMES_WEBUI_HOST=0.0.0.0
ENV HERMES_WEBUI_PORT=8787
ENV HERMES_WEBUI_AGENT_DIR=/opt/hermes
ENV HERMES_WEBUI_SEED_BUNDLED_SKILLS=auto
ENV HERMES_BUNDLED_SKILLS=/opt/hermes/skills
ENV PYTHONPATH=/opt/hermes
# Feishu OAuth app credentials are per deployment instance. Keep the
# Hermes-Expert image reusable: do not bake FEISHU_APP_ID or FEISHU_APP_SECRET
# through build args or image ENV values. Pass them at runtime with docker
# compose/.env or `docker run -e`; the login page shows the Feishu button only
# when both runtime variables are present.
EXPOSE 8787
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
CMD curl -f http://localhost:8787/health || exit 1
USER 0
CMD ["/hermeswebui_init.bash"]
关键结构如下:
dockerfile
ARG HERMES_AGENT_IMAGE=nousresearch/hermes-agent:latest
FROM ${HERMES_AGENT_IMAGE} AS hermes-agent-source
FROM python:3.12-slim
# 省略系统依赖、用户、uv、WebUI 源码复制等已有步骤
COPY --from=hermes-agent-source --chown=root:root /opt/hermes /opt/hermes
RUN chmod -R a+rX /opt/hermes
COPY --chown=root:root . /apptoo
RUN rm -rf /apptoo/venv /apptoo/.venv /apptoo/env /apptoo/.env /apptoo/node_modules
ENV HERMES_WEBUI_AGENT_DIR=/opt/hermes
ENV HERMES_WEBUI_SEED_BUNDLED_SKILLS=auto
这些点是集成核心:
ARG HERMES_AGENT_IMAGE:允许构建时替换 Agent 镜像,默认使用nousresearch/hermes-agent:latest。FROM ${HERMES_AGENT_IMAGE} AS hermes-agent-source:先把 Agent 镜像作为源码来源阶段。COPY --from=hermes-agent-source /opt/hermes /opt/hermes:把 Agent 源码打入最终 WebUI 镜像。RUN rm -rf /apptoo/venv ...:防止本地旧虚拟环境被复制进镜像,导致启动时复用旧依赖。HERMES_WEBUI_SEED_BUNDLED_SKILLS=auto:每个实例/profile 首次启动时把 Agent bundled skills 补种到运行时挂载的
/home/hermeswebui/.hermes/skills。注意 Docker bind mount 会覆盖镜像内同路径内容,
所以不能只在构建期复制 skills。补种完成后会写入.webui_bundled_skills_seeded
标记,后续重启不会重复补种。
同时建议 .dockerignore 排除本地虚拟环境和构建产物:
dockerignore
venv/
.venv/
env/
.env/
node_modules/
dist/
build/
如需让 APT 使用国内源,建议在 Dockerfile 中增加可配置参数,而不是写死到所有环境:
dockerfile
ARG APT_MIRROR=https://mirrors.tuna.tsinghua.edu.cn/debian
RUN sed -i "s#http://deb.debian.org/debian#${APT_MIRROR}#g" /etc/apt/sources.list.d/debian.sources \
&& sed -i "s#http://security.debian.org/debian-security#${APT_MIRROR}-security#g" /etc/apt/sources.list.d/debian.sources
如果基础镜像已经预处理过 APT 源,可以不再修改 Dockerfile。
5. 构建命令
在 hermes-webui 代码目录执行,集成之后的 docker image,是 hermes-expert-webui,即:
bash
export IMAGE_VERSION=v1.0.10
docker build \
-f Dockerfile.hermes-webui-single-image \
--platform linux/amd64 \
--build-arg HERMES_AGENT_IMAGE=nousresearch/hermes-agent:latest \
--build-arg HERMES_VERSION=${IMAGE_VERSION} \
--build-arg APT_MIRROR=https://mirrors.tuna.tsinghua.edu.cn/debian \
-t hermes-expert-webui:${IMAGE_VERSION} \
-t hermes-expert-webui:latest \
.
构建完成后检查镜像:
bash
docker images | grep hermes-expert-webui
如需推送到企业镜像仓库,由部署人员自行替换为真实仓库地址:
bash
export TARGET_IMAGE=your-registry.example.com/your-namespace/hermes-expert-webui:${IMAGE_VERSION}
docker tag hermes-expert-webui:${IMAGE_VERSION} ${TARGET_IMAGE}
docker push ${TARGET_IMAGE}
企业镜像仓库推送示例仍使用占位符。不要把真实账号、密码或仓库命名空间写入本文档、
命令历史或 Git:
bash
docker login your-registry.example.com
docker tag hermes-expert-webui:${IMAGE_VERSION} \
your-registry.example.com/your-namespace/hermes-expert-webui:${IMAGE_VERSION}
docker push your-registry.example.com/your-namespace/hermes-expert-webui:${IMAGE_VERSION}
docker logout your-registry.example.com
远程服务器拉取企业镜像示例:
bash
docker login your-registry.example.com
export IMAGE_VERSION=v1.0.10
docker pull your-registry.example.com/your-namespace/hermes-expert-webui:${IMAGE_VERSION}
docker tag your-registry.example.com/your-namespace/hermes-expert-webui:${IMAGE_VERSION} \
hermes-expert-webui:${IMAGE_VERSION}
docker images | grep hermes-expert-webui
docker logout your-registry.example.com
6. 单容器多实例运行命令
同一个 hermes-expert-webui:<版本号> 镜像可以启动多个独立实例,例如 online、test 和不同用户实例。每个实例必须独立使用:
| 项 | 规则 | 示例 |
|---|---|---|
| 容器名 | hermes-webui-<实例名> |
hermes-webui-online |
| 宿主机端口 | 每个实例不同 | 13001、13002、13003 |
| Hermes 状态目录 | 每个实例不同 | /data/hermes-webui/online/.hermes |
| 工作区目录 | 每个实例不同 | /data/hermes-webui/online/workspace |
| 登录配置文件 | 每个实例不同,权限 600 |
/data/hermes-webui/online/auth.env |
| 容器内端口 | 固定 8787,宿主机绑定所有网卡 |
-p 0.0.0.0:13001:8787 |
推荐实例规划:
| 实例名 | 容器名 | 宿主机端口 | 状态目录 | 工作区目录 |
|---|---|---|---|---|
online |
hermes-webui-online |
13001 |
/data/hermes-webui/online/.hermes |
/data/hermes-webui/online/workspace |
test |
hermes-webui-test |
13002 |
/data/hermes-webui/test/.hermes |
/data/hermes-webui/test/workspace |
user01 |
hermes-webui-user01 |
13011 |
/data/hermes-webui/user01/.hermes |
/data/hermes-webui/user01/workspace |
先创建目录并设置权限:
bash
export HERMES_EXPERT_BASE=/data/hermes-webui
export HERMES_UID=1000
export HERMES_GID=1000
export HERMES_EXPERT_INSTANCES="online:13001 test:13002"
for item in ${HERMES_EXPERT_INSTANCES}; do
instance="${item%%:*}"
mkdir -p "${HERMES_EXPERT_BASE}/${instance}/.hermes"
mkdir -p "${HERMES_EXPERT_BASE}/${instance}/workspace"
touch "${HERMES_EXPERT_BASE}/${instance}/auth.env"
chmod 600 "${HERMES_EXPERT_BASE}/${instance}/auth.env"
chown -R "${HERMES_UID}:${HERMES_GID}" "${HERMES_EXPERT_BASE}/${instance}"
done
每个实例单独维护登录配置。下面只写占位符,部署时在服务器本地替换:
bash
umask 077
cat > /data/hermes-webui/online/auth.env <<'EOF'
# online 实例:账号密码 + 飞书登录都走 HTTP 直连
HERMES_WEBUI_ACCOUNT_LOGIN_ENABLED=1
HERMES_WEBUI_PASSWORD_LOGIN_ENTRY_ENABLED=1
FEISHU_APP_ID=cli_online_app_id
FEISHU_APP_SECRET=replace_with_online_secret
FEISHU_REDIRECT_URI=http://xx.xx.xx.xx:13001/api/auth/feishu/callback
EOF
cat > /data/hermes-webui/test/auth.env <<'EOF'
# test 实例:账号密码 + 飞书登录都走 HTTP 直连
HERMES_WEBUI_ACCOUNT_LOGIN_ENABLED=1
HERMES_WEBUI_PASSWORD_LOGIN_ENTRY_ENABLED=1
FEISHU_APP_ID=cli_test_app_id
FEISHU_APP_SECRET=replace_with_test_secret
FEISHU_REDIRECT_URI=http://xx.xx.xx.xx:13002/api/auth/feishu/callback
EOF
部署时把 xx.xx.xx.xx 替换为浏览器实际访问 WebUI 的服务器 IP 或域名,把端口替换为该实例映射的宿主机端口。当前部署约定是直接使用 HTTP 登录账号密码与飞书;不要在这些 HTTP 实例里设置 HERMES_WEBUI_SECURE=1,否则浏览器不会在 HTTP 页面发送 Secure 登录 Cookie。如果某个实例只使用账号密码登录,不需要飞书按钮,可以在该实例的 auth.env 中不设置 FEISHU_APP_ID / FEISHU_APP_SECRET,或把它们留空。
定义一个通用启动函数:
bash
export IMAGE_VERSION=v1.0.10
export HERMES_EXPERT_IMAGE=hermes-expert-webui:${IMAGE_VERSION}
export HERMES_EXPERT_BASE=/data/hermes-webui
export HERMES_UID=1000
export HERMES_GID=1000
export HERMES_EXPERT_INSTANCES="online:13001 test:13002"
start_hermes_expert() {
instance="$1"
host_port="$2"
container_name="hermes-webui-${instance}"
instance_home="${HERMES_EXPERT_BASE}/${instance}/.hermes"
instance_workspace="${HERMES_EXPERT_BASE}/${instance}/workspace"
instance_auth_env="${HERMES_EXPERT_BASE}/${instance}/auth.env"
mkdir -p "${instance_home}" "${instance_workspace}"
if [ ! -f "${instance_auth_env}" ]; then
umask 077
touch "${instance_auth_env}"
fi
chmod 600 "${instance_auth_env}"
chown -R "${HERMES_UID}:${HERMES_GID}" "${HERMES_EXPERT_BASE}/${instance}"
docker rm -f "${container_name}" 2>/dev/null || true
docker run -d \
--name "${container_name}" \
--restart unless-stopped \
--user 0:0 \
--env-file "${instance_auth_env}" \
-p "0.0.0.0:${host_port}:8787" \
-v "${instance_home}:/home/hermeswebui/.hermes" \
-v "${instance_workspace}:/workspace" \
-e WANTED_UID="${HERMES_UID}" \
-e WANTED_GID="${HERMES_GID}" \
-e HERMES_WEBUI_HOST=0.0.0.0 \
-e HERMES_WEBUI_PORT=8787 \
-e HERMES_WEBUI_STATE_DIR=/home/hermeswebui/.hermes/webui \
-e HERMES_WEBUI_AGENT_DIR=/opt/hermes \
-e HERMES_WEBUI_SEED_BUNDLED_SKILLS=auto \
-e PYTHONPATH=/opt/hermes \
-e UV_INDEX_URL=https://pypi.tuna.tsinghua.edu.cn/simple \
-e UV_DEFAULT_INDEX=https://pypi.tuna.tsinghua.edu.cn/simple \
-e PIP_INDEX_URL=https://pypi.tuna.tsinghua.edu.cn/simple \
"${HERMES_EXPERT_IMAGE}"
}
启动实例列表中的全部实例:
bash
for item in ${HERMES_EXPERT_INSTANCES}; do
instance="${item%%:*}"
host_port="${item##*:}"
start_hermes_expert "${instance}" "${host_port}"
done
访问地址:
text
online: http://127.0.0.1:13001 # 本机或 SSH 隧道
test: http://127.0.0.1:13002
online: http://<服务器内网 IP>:13001 # 局域网访问
test: http://<服务器内网 IP>:13002
CentOS 7 如果启用了 firewalld,还需要开放端口:
bash
firewall-cmd --permanent --add-port=13001/tcp
firewall-cmd --permanent --add-port=13002/tcp
firewall-cmd --reload
检查 Docker 是否已经监听到所有网卡:
bash
docker ps --format 'table {{.Names}}\t{{.Ports}}' | grep hermes-webui-
ss -lntp | grep -E ':13001|:13002'
如果需要为不同实例配置不同登录方式或模型供应商,在对应实例的 ${HERMES_EXPERT_BASE}/${instance}/.hermes 下维护独立配置。不要把真实密码、API Key、OAuth 密钥或完整 .env 文件写入本文档。当前验收部署可以直接使用 HTTP 账号密码和飞书登录;如果要暴露到公网,至少保留强密码或飞书绑定控制,后续再按企业要求补 HTTPS / 可信反向代理。
6.1 飞书登录按钮显示条件
登录页只有在 /api/auth/status 返回 feishu_enabled: true 时才显示飞书登录按钮。WebUI 后端的判断条件很简单:当前容器环境中同时存在非空的FEISHU_APP_ID 和 FEISHU_APP_SECRET。因此飞书应用凭证必须按实例传入,不能写成全局共享配置,更不能在构建镜像时写入 Dockerfile。
推荐规则:
- 同一镜像可以跑多个实例。
- 每个实例使用自己的
${HERMES_EXPERT_BASE}/${instance}/auth.env。 - 每个实例可以绑定不同的飞书开放平台应用。
- 只有配置了
FEISHU_APP_ID和FEISHU_APP_SECRET的实例才显示飞书登录按钮。 - 没有配置飞书凭证的实例不会显示飞书登录按钮,但仍可按密码登录配置工作。
示例:
bash
umask 077
cat > /data/hermes-webui/online/auth.env <<'EOF'
HERMES_WEBUI_ACCOUNT_LOGIN_ENABLED=1
HERMES_WEBUI_PASSWORD_LOGIN_ENTRY_ENABLED=1
FEISHU_APP_ID=cli_online_app_id
FEISHU_APP_SECRET=replace_with_online_secret
FEISHU_REDIRECT_URI=http://xx.xx.xx.xx:13001/api/auth/feishu/callback
EOF
即使本文档位于已被 Git 忽略的 mydocs/ 目录,也不要把真实 FEISHU_APP_SECRET 上传、截图、打包进镜像或复制到公开工单。
飞书登录不再使用额外的飞书用户名单环境变量。回调后的访问授权只通过用户管理里的本地账号 feishu_open_id 绑定控制。每个实例的回调地址不同,例如:
text
online: http://xx.xx.xx.xx:13001/api/auth/feishu/callback
test: http://xx.xx.xx.xx:13002/api/auth/feishu/callback
当前部署暂时按 HTTP 直连使用账号密码与飞书登录,回调地址必须与浏览器实际访问地址完全一致,包括协议、IP/域名、端口和路径。需要在每个飞书开放平台应用后台把该实例的实际 HTTP 回调地址加入允许列表。修改 FEISHU_*环境变量后必须删除并重建容器,单纯 docker restart 不会改变容器环境变量。
7. 运行后检查
查看容器:
bash
docker ps --filter "name=hermes-webui-"
查看健康状态:
bash
for item in ${HERMES_EXPERT_INSTANCES}; do
instance="${item%%:*}"
port="${item##*:}"
echo "== ${port} =="
curl -fsS "http://127.0.0.1:${port}/health"
echo
done
查看日志:
bash
for item in ${HERMES_EXPERT_INSTANCES}; do
instance="${item%%:*}"
echo "== hermes-webui-${instance} =="
docker logs --tail=120 "hermes-webui-${instance}"
done
持续跟踪某个实例日志:
bash
docker logs -f --tail=200 hermes-webui-online
持续跟踪多个实例日志:
bash
for item in ${HERMES_EXPERT_INSTANCES}; do
instance="${item%%:*}"
container_name="hermes-webui-${instance}"
echo "== ${container_name} =="
docker logs --tail=80 "${container_name}"
done
如果容器启动后立即退出,查看退出状态和错误:
bash
docker ps -a --filter "name=hermes-webui-"
docker inspect hermes-webui-online \
--format 'status={{.State.Status}} exit={{.State.ExitCode}} error={{.State.Error}} started={{.State.StartedAt}} finished={{.State.FinishedAt}}'
docker logs --tail=300 hermes-webui-online
如果容器已经正常运行,也可以进入容器查看 WebUI 状态目录日志:
bash
docker exec -it hermes-webui-online sh -lc '
echo "HERMES_WEBUI_STATE_DIR=${HERMES_WEBUI_STATE_DIR}";
find "${HERMES_WEBUI_STATE_DIR}" -maxdepth 1 -type f -name "*.log" -print;
tail -n 200 "${HERMES_WEBUI_STATE_DIR}"/bootstrap-*.log 2>/dev/null || true
'
确认 Agent 源码已进入镜像:
bash
docker exec hermes-webui-online sh -lc 'test -f /opt/hermes/pyproject.toml && echo agent-source-ok'
docker exec hermes-webui-online sh -lc 'python - <<PY
import sys
sys.path.insert(0, "/opt/hermes")
from run_agent import AIAgent
print("agent-import-ok")
PY'
8. 首次配置大模型
WebUI 后端使用容器内置的 Hermes Agent。每个实例都有独立的 /home/hermeswebui/.hermes 挂载目录,因此大模型 Provider、默认模型、Base URL
和凭据也需要按实例分别配置。进入 online 实例配置大模型:
bash
docker exec -it hermes-webui-online sh -lc '
export HERMES_HOME=/home/hermeswebui/.hermes
export UV_INDEX_URL=https://pypi.tuna.tsinghua.edu.cn/simple
export UV_DEFAULT_INDEX=https://pypi.tuna.tsinghua.edu.cn/simple
export PIP_INDEX_URL=https://pypi.tuna.tsinghua.edu.cn/simple
if [ -x /app/venv/bin/hermes ]; then
/app/venv/bin/hermes model
else
hermes model
fi
'
进入 test 实例配置大模型:
bash
docker exec -it hermes-webui-test sh -lc '
export HERMES_HOME=/home/hermeswebui/.hermes
export UV_INDEX_URL=https://pypi.tuna.tsinghua.edu.cn/simple
export UV_DEFAULT_INDEX=https://pypi.tuna.tsinghua.edu.cn/simple
export PIP_INDEX_URL=https://pypi.tuna.tsinghua.edu.cn/simple
if [ -x /app/venv/bin/hermes ]; then
/app/venv/bin/hermes model
else
hermes model
fi
'
如果使用 OpenAI-compatible 本地模型服务,模型服务运行在宿主机上:
- Docker Desktop 可使用
http://host.docker.internal:<端口>/v1。 - Linux 服务器建议使用宿主机网桥地址、局域网地址,或启动容器时增加
--add-host=host.docker.internal:host-gateway后使用http://host.docker.internal:<端口>/v1。 - 不要在容器内把宿主机模型服务写成
http://127.0.0.1:<端口>/v1,除非模型服务也在同一个容器里。
配置后检查每个实例是否生成配置文件:
bash
for item in ${HERMES_EXPERT_INSTANCES}; do
instance="${item%%:*}"
echo "== hermes-webui-${instance} =="
docker exec "hermes-webui-${instance}" sh -lc '
export HERMES_HOME=/home/hermeswebui/.hermes
test -f "${HERMES_HOME}/config.yaml" && echo "config.yaml exists" || echo "config.yaml missing"
'
done
不要把 API Key、OAuth Token、密码或完整 .env / auth.json 内容打印到聊天、文档或工单中。
9. 关键环境变量说明
| 变量 | 位置 | 说明 |
|---|---|---|
HERMES_AGENT_IMAGE |
构建参数 | 指定 Agent 源码镜像,默认 nousresearch/hermes-agent:latest |
HERMES_VERSION |
构建参数 | 写入 WebUI 版本信息,便于设置页显示 |
HERMES_WEBUI_STATE_DIR |
运行环境 | WebUI 会话、设置、索引等状态目录 |
HERMES_WEBUI_AGENT_DIR |
运行环境 | WebUI 查找 Agent 源码的路径,单镜像固定为 /opt/hermes |
HERMES_WEBUI_SEED_BUNDLED_SKILLS |
运行环境 | auto 时按实例/profile 首次补种 Agent bundled skills 并写 marker;设为 0 可关闭 |
HERMES_WEBUI_ALLOW_INSECURE_REMOTE_AUTH |
运行环境 | 兼容保留变量;当前账号密码和飞书登录已允许 HTTP 直连,通常不需要设置 |
HERMES_WEBUI_SECURE |
运行环境 | 只在 HTTPS 或 HTTPS 反向代理下设为 1;HTTP 直连实例不要设置,否则浏览器不会发送登录 Cookie |
FEISHU_APP_ID |
运行环境 | 飞书开放平台应用 ID,和 FEISHU_APP_SECRET 同时配置后显示飞书登录 |
FEISHU_APP_SECRET |
运行环境 | 飞书开放平台应用密钥,不要写入仓库 |
FEISHU_REDIRECT_URI |
运行环境 | 飞书 OAuth 回调地址,需与飞书开放平台应用后台允许列表一致 |
WANTED_UID |
运行环境 | 容器内运行用户 UID,建议与宿主机挂载目录所有者一致 |
WANTED_GID |
运行环境 | 容器内运行用户 GID,建议与宿主机挂载目录所有组一致 |
UV_INDEX_URL |
运行环境 | uv 安装依赖时使用的 Python 包索引 |
UV_DEFAULT_INDEX |
运行环境 | uv 默认 Python 包索引 |
PIP_INDEX_URL |
运行环境 | pip 使用的 Python 包索引 |
HERMES_HOME |
CLI 环境 | 运行 hermes model 时指向当前实例的 Hermes 状态目录 |
ENV_OBFUSCATE_PART 是容器入口脚本 docker_init.bash 的内部脱敏规则,当前镜像内默认已经设置为 TOKEN API KEY SECRET PASSWORD。它用于在入口脚本把 root 阶段的运行时环境变量传递给 hermeswebui 用户时,将变量名包含这些片段的值打印为OBFUSCATED,避免 FEISHU_APP_SECRET、密码或模型 Key 出现在启动日志里。正常部署不需要在 docker run 或 auth.env 中手动配置它。如果日志提示ENV_OBFUSCATE_PART not set,优先检查是否正在运行旧镜像、旧 docker_init.bash,或镜像没有用最新代码重新构建。
10. 故障处理
10.1 镜像拉取失败
先单独拉取,定位到底是 Python 基础镜像还是 Agent 镜像失败:
bash
docker pull python:3.12-slim
docker pull nousresearch/hermes-agent:latest
如果失败:
- 确认服务器 DNS、代理、防火墙和 Docker Hub / Agent 镜像来源可访问。
- 如果当前网络无法访问,先在可访问环境中
docker pull后docker save,再在目标机器docker load。 - 如果使用企业镜像代理,导入后仍可打回默认 tag,保持 Dockerfile 和命令不变。
10.2 Docker build 中仍尝试访问海外镜像
检查构建命令是否传入:
bash
--build-arg HERMES_AGENT_IMAGE=nousresearch/hermes-agent:latest
检查 Dockerfile 是否仍写死:
dockerfile
FROM nousresearch/hermes-agent:latest
FROM python:3.12-slim
如果镜像是从离线包或企业代理导入的,可提前打回 python:3.12-slim 和 nousresearch/hermes-agent:latest 本地别名。
10.3 APT 安装失败
如果构建日志卡在 apt-get update,优先处理 Debian 源:
- 使用已预处理 APT 源的
python:3.12-slim基础镜像。 - 或在 Dockerfile 中通过
APT_MIRROR=https://mirrors.tuna.tsinghua.edu.cn/debian替换源。 - 或配置内网 apt-cacher-ng,再通过
BUILD_APT_PROXY传入代理。
10.4 uv / PyPI 依赖安装失败
确认容器运行环境传入了国内 Python 源:
bash
docker exec hermes-webui-online sh -lc 'env | grep -E "UV_INDEX_URL|UV_DEFAULT_INDEX|PIP_INDEX_URL"'
如果检查的不是 online 实例,把容器名替换为实际实例名,例如 hermes-webui-test 或 hermes-webui-user01。
建议值:
text
UV_INDEX_URL=https://pypi.tuna.tsinghua.edu.cn/simple
UV_DEFAULT_INDEX=https://pypi.tuna.tsinghua.edu.cn/simple
PIP_INDEX_URL=https://pypi.tuna.tsinghua.edu.cn/simple
如果某些包在镜像源同步延迟,可以临时切换企业内网 PyPI 缓存源。
10.5 Agent 源码未打入镜像
症状通常是 WebUI 日志出现 Agent source not found 或 AIAgent not available。
检查:
bash
docker exec hermes-webui-online ls -la /opt/hermes
docker exec hermes-webui-online test -f /opt/hermes/pyproject.toml
处理:
- 确认 Dockerfile 中存在
FROM ${HERMES_AGENT_IMAGE} AS hermes-agent-source。 - 确认 Dockerfile 中存在
COPY --from=hermes-agent-source /opt/hermes /opt/hermes。 - 确认构建参数
HERMES_AGENT_IMAGE指向的镜像内确实存在/opt/hermes。
10.6 Failed to load plugin 'nous'
如果日志出现:
text
== Running hermes-webui
Failed to load plugin 'nous': No module named 'hermes_cli.dashboard_auth'
优先检查当前容器实际加载的 hermes_cli 来源:
bash
docker exec hermes-webui-online sh -lc '
echo "python=$(command -v python)"
echo "PYTHONPATH=${PYTHONPATH:-}"
python - <<PY
import importlib.util
for m in ["hermes_cli", "hermes_cli.dashboard_auth"]:
s = importlib.util.find_spec(m)
print(m, "=>", s.origin if s else "MISSING")
PY
ls -la /opt/hermes/hermes_cli/dashboard_auth 2>&1 || true
find /apptoo -maxdepth 2 \( -path "/apptoo/venv" -o -path "/apptoo/.venv" -o -name ".deps_installed" \) -print
'
如果 /opt/hermes/hermes_cli/dashboard_auth 存在,但 Python 仍提示缺失,通常是运行时没有把 /opt/hermes 放入 PYTHONPATH,或镜像/容器里复用了旧 /app/venv。当前 Dockerfile 和启动命令已设置:
bash
-e PYTHONPATH=/opt/hermes
处理步骤:
bash
docker exec hermes-webui-online sh -lc 'rm -rf /app/venv'
docker restart hermes-webui-online
docker logs -f --tail=200 hermes-webui-online
如果 find /apptoo ... 能看到 /apptoo/venv 或 /apptoo/.venv,说明构建时把本地虚拟环境打进了镜像,需要更新 .dockerignore 后重新构建并重启实例。当前 Dockerfile 已额外执行:
dockerfile
RUN rm -rf /apptoo/venv /apptoo/.venv /apptoo/env /apptoo/.env /apptoo/node_modules
10.7 UID/GID 权限问题
症状包括 .hermes、config.yaml、webui 状态目录或 /workspace 无法读写。
检查宿主机目录:
bash
ls -ld /data/hermes-webui/online/.hermes /data/hermes-webui/online/workspace
检查容器 UID/GID:
bash
docker exec hermes-webui-online id
处理:
bash
chown -R 1000:1000 /data/hermes-webui/online/.hermes
chown -R 1000:1000 /data/hermes-webui/online/workspace
如果宿主机用户不是 1000:1000,把 WANTED_UID、WANTED_GID 和目录所有者改成实际值。如果检查的是其他实例,把 online 替换为实际实例名。
10.8 容器内 localhost 误用
容器内的 localhost 指容器自己,不是宿主机。如果 WebUI 或模型配置需要访问宿主机上的本地模型服务:
- Docker Desktop 可使用
http://host.docker.internal:<端口>/v1。 - Linux 服务器可使用宿主机网桥地址、局域网地址,或显式增加
--add-host=host.docker.internal:host-gateway。 - 不要把宿主机服务地址写成容器内的
http://127.0.0.1:<端口>,除非服务就在同一个容器中。
10.9 HTTP 登录和 Secure Cookie
当前版本允许通过 http://服务器IP:端口 直接使用账号密码登录和飞书登录,不需要再额外设置HERMES_WEBUI_ALLOW_INSECURE_REMOTE_AUTH=1。如果 HTTP 页面登录后仍停留在登录页,优先检查是否误设了:
bash
HERMES_WEBUI_SECURE=1
HERMES_WEBUI_SECURE=1 会让服务端下发 Secure 登录 Cookie,而浏览器不会在普通 HTTP 页面保存或发送这类 Cookie。HTTP 直连部署应保持该变量未设置。
检查某个实例的运行环境:
bash
docker exec hermes-webui-online sh -lc 'env | grep -E "HERMES_WEBUI_SECURE|HERMES_WEBUI_ALLOW_INSECURE_REMOTE_AUTH|FEISHU_REDIRECT_URI" || true'
如果输出里有 HERMES_WEBUI_SECURE=1,删除并重建容器。飞书登录失败时,同时确认 FEISHU_REDIRECT_URI 与飞书开放平台后台允许列表完全一致,例如 http://xx.xx.xx.xx:13001/api/auth/feishu/callback。