LLM - 集成 Hermes Agent 与 WebUI 至同一个 Docker 镜像配置

欢迎关注我的CSDN:https://spike.blog.csdn.net/

本文地址:https://spike.blog.csdn.net/article/details/161541685

免责声明:本文来源于个人知识与公开资料,仅用于学术交流,欢迎讨论,不支持转载。


在国内环境下构建并运行 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 源导致的失败。

1. 目标与适用范围

适用场景:

  • 在国内服务器、办公网络或 CI 环境中构建 Hermes Agent 与 WebUI 镜像。
  • 使用默认公开镜像名构建:python:3.12-slimnousresearch/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_IDFEISHU_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:<版本号> 镜像可以启动多个独立实例,例如 onlinetest 和不同用户实例。每个实例必须独立使用:

规则 示例
容器名 hermes-webui-<实例名> hermes-webui-online
宿主机端口 每个实例不同 130011300213003
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_IDFEISHU_APP_SECRET。因此飞书应用凭证必须按实例传入,不能写成全局共享配置,更不能在构建镜像时写入 Dockerfile。

推荐规则:

  • 同一镜像可以跑多个实例。
  • 每个实例使用自己的 ${HERMES_EXPERT_BASE}/${instance}/auth.env
  • 每个实例可以绑定不同的飞书开放平台应用。
  • 只有配置了 FEISHU_APP_IDFEISHU_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 runauth.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 pulldocker 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-slimnousresearch/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-testhermes-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 权限问题

症状包括 .hermesconfig.yamlwebui 状态目录或 /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_UIDWANTED_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:<端口>,除非服务就在同一个容器中。

当前版本允许通过 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

相关推荐
杨浦老苏2 小时前
网络连接实时可视化利器TapMap
网络·docker·可视化·监控·群晖
香气袭人知骤暖3 小时前
PG数据库 Docker 容器自动备份方案
数据库·docker·容器
AI服务老曹3 小时前
解耦异构算力:基于 Docker 与 GB28181/RTSP 的边缘计算 AI 视频管理平台架构设计与源码交付实践
人工智能·docker·边缘计算
weixin_468466853 小时前
Prometheus监控服务部署与实战指南
服务器·后端·python·docker·自动化·prometheus
ai产品老杨5 小时前
基于 Docker 容器化与异构计算的智能安防架构:解耦 GB28181/RTSP 协议与多芯片适配,源码交付如何助力集成商节省 95% 开发成本?
docker·容器·架构
Plastic garden5 小时前
Docker(2)网络模式
运维·docker·容器
“码”力全开9 小时前
突破异构算力与协议围墙:基于 Docker 与边缘计算的 GB28181/RTSP 视频智能管理平台架构实践(附源码交付)
docker·音视频·边缘计算
java_logo9 小时前
Docker 部署 GitLab CE 完整版教程
docker·容器·gitlab·gitlab docker部署·gitlab部署文档·gitlab部署·gitlab部署教程