折腾是程序员的浪漫,踩坑是成长的代价。这篇文章记录了我在 Ubuntu 上把 Open WebUI 和 Hermes Agent 连起来的全过程,包括每一个「为什么这里会报错」的瞬间。
为什么是 Open WebUI
Hermes Agent 很强,但默认主要还是围绕命令行和网关模式使用。
它聪明、灵活、能做事,也很有“代理感”——不像传统聊天模型那样只是回答问题,它更像一个真的会帮你处理任务的小伙伴。只是,命令行终究还是命令行。对于喜欢终端的人来说,那种黑底白字当然很迷人;可有些时候,我还是会想,如果它能有一个更柔和一点、更直观一点、像 ChatGPT 一样的界面就好了。
Open WebUI 则更像是一个“壳”,一个足够体面的交互层。它的好处很直接:
有可视化聊天界面,适合长期使用。
支持多模型/多连接管理。
支持 OpenAI 兼容 API,因此天然适合对接 Hermes API Server。
换句话说,Hermes 负责“脑子”和“执行”,Open WebUI 负责“脸”和“入口”。这套组合搭好以后,整个体验会从“我在调一个强大的 CLI 工具”,变成“我拥有了一个属于自己的智能工作台”。
这套方案的整体结构
在开始操作之前,我们先把整个调用链看清楚。理解结构,后面的坑会少很多。
浏览器
↓
Open WebUI(Docker 容器)
↓
Hermes API Server(Ubuntu 宿主机)
↓
Hermes 内部配置的云端模型 API(或者本地部署的模型)
↓
OpenAI / Anthropic / OpenRouter / 其他兼容模型服务
这里最关键的一点是:
Open WebUI 并不直接调用你的云模型(或者本地模型)。
它调用的是 Hermes 暴露出来的 API Server;再由 Hermes 去调用真正的模型 API。
这也意味着,Open WebUI 里填的那个 API Key,不是你的 OpenAI Key,也不是 Claude Key,而是你给 Hermes API Server 自己设的一把“门钥匙”。
开始之前,你需要准备什么
建议先确认下面几件事:
Ubuntu 系统已经可正常联网。
Hermes Agent 已经安装完成,并且底层模型已经能跑通。
你有 sudo 权限。
3000、8080、8642 这几个端口里,至少有一部分没有被占用。(ps:注意,不一定是这几个端口,只要是没被占用的端口其实都可以,看自己选择)
你愿意接受:Docker、网络、权限、镜像源,这几个关键词总会有一个来折磨你一下。
如果你还没确认端口是否空闲,可以先检查一下:
ss -tlnp | grep -E "3000|8080|8642|9000"
没有输出,说明端口空闲;有输出,说明被占用了,需要换端口或者停掉对应进程。
查看所有正在监听的端口(了解全局)
ss -tlnp
在 Ubuntu 上安装 Docker
更新包索引并安装依赖
sudo apt update
sudo apt install -y ca-certificates curl gnupg
# 添加 Docker 官方 GPG 密钥
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | \
sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg
添加 Docker 官方仓库
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \
https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
安装 Docker Engine 和 Compose 插件
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
把当前用户加入 docker 组
sudo usermod -aG docker $USER
newgrp docker
验证:
docker --version
docker compose version
确认 Hermes API Server 可以工作
这一部分是核心。
如果说 Open WebUI 是一个精致的前台,那么 Hermes API Server 就是后厨窗口。前台再漂亮,后厨窗口不开,也什么都送不出来。
Hermes 需要在 ~/.hermes/.env 中启用 API Server。
打开配置文件:
vim ~/.hermes/.env
加入下面几项(端口和api key 可以自己设计, 同时host ip地址也可以自己修改):
API_SERVER_ENABLED=true
API_SERVER_PORT=8642
API_SERVER_HOST=0.0.0.0
API_SERVER_KEY=你的hermes的api-key,自己设置一个,最好复杂一点
这些变量分别是什么意思:
| 变量 | 作用 |
|---|---|
API_SERVER_ENABLED | 是否启用 Hermes 的 API Server |
API_SERVER_PORT | API Server 监听端口,常用 8642 |
API_SERVER_HOST | 监听地址,决定谁能访问它 |
API_SERVER_KEY | 你自己定义的一把门禁密钥,不是模型厂商的 Key |
API_SERVER_KEY。它不是 OpenAI 的 API Key,也不是你接入云模型的 Key。它只是 Hermes 暴露 HTTP 接口时,用来验证“谁可以访问我”的一串密码。
你可以理解成这样:
Open WebUI 带着这串 Key 去敲门
↓
Hermes 看一眼:哦,是你,进来吧
↓
Hermes 再拿着它自己内部配置好的模型 Key 去请求真正的大模型
所以这串 Key 是你自己定的,只要 Open WebUI 那边填写同样的值即可。
为什么这里建议用 0.0.0.0,而不是 127.0.0.1
这是后面大部分网络问题的源头,所以我单独拎出来说。
127.0.0.1表示“只接受本机回环地址请求”。0.0.0.0表示“监听所有网络接口”。
如果 Hermes 只监听 127.0.0.1,那么 Ubuntu 宿主机自己 curl localhost:8642 没问题,但 Docker 容器里的 Open WebUI 来访问时,很可能会被拒绝,因为容器并不是宿主机自己的回环接口。
所以,在 Docker 中的 Open WebUI 需要连接宿主机 Hermes 的场景里,最省心的做法就是:
API_SERVER_HOST=0.0.0.0
这是本文后面所有网络排障建议里的首选方案。
127.0.0.1 和0.0.0.0 对于open webui的容器的配置要求是不一样的,如果是前者,那么端口映射宿主机必须是8080,后者可以自己定义。
启动 Hermes Gateway
启动命令很简单:
hermes gateway
开一个新终端,执行:
curl http://localhost:8642/health
如果返回:{“status”: “ok”, “platform”: “hermes-agent”}。
就说明 Hermes API Server 已经活了。
这是一个非常重要的检查点。
只要这一步没通,后面别急着配 Open WebUI。
编写 Docker Compose,部署 Open WebUI
新建目录:
mkdir -p ~/open-webui
cd ~/open-webui
创建 docker-compose.yml:
services:
open-webui:
image: openwebui/open-webui:latest
container_name: open-webui
ports:
- "3000:8080"
volumes:
- open-webui:/app/backend/data
environment:
# 指向宿主机上的 Hermes API Server
- OPENAI_API_BASE_URL=http://host.docker.internal:8642/v1
# 必须与 Hermes 的 API_SERVER_KEY 一致
- OPENAI_API_KEY=你前面配置的hermes的api key
# 持久化 Secret Key,防止容器重建后被登出
- WEBUI_SECRET_KEY=your-webui-secret # 用 openssl rand -hex 32 生成
# Linux 无 Docker Desktop 时必须加这行,让容器能解析 host.docker.internal
extra_hosts:
- "host.docker.internal:host-gateway"
restart: always
volumes:
open-webui:
这里面有几个地方值得单独解释。
ports: “3000:8080” 是什么意思: Docker 里的端口映射格式是:宿主机端口:容器内部端口。
也就是说:
左边
3000是你在浏览器里访问的端口,可以改。右边
8080是 Open WebUI 容器内部监听的端口,通常不要改。
比如你想改成 8000:
ports:
– “8000:8080”
那么浏览器就访问 http://localhost:8000。
WEBUI_SECRET_KEY:每次登录 Open WebUI 后,系统会生成一个登录令牌(JWT),让你保持登录状态。WEBUI_SECRET_KEY 就是用来签名和验证这个令牌的”底层钥匙”。在终端输入:
openssl rand -hex 32
把得到的字符串填进去就好了。
如果在hermes 里面填写的地址是127.0.0.1 ,那么 docker-compose.yml 可以改为:
services:
open-webui:
image: ghcr.io/open-webui/open-webui:main
container_name: open-webui
network_mode: host # 共享宿主机网络
volumes:
- open-webui:/app/backend/data
environment:
- OPENAI_API_BASE_URL=http://localhost:8642/v1 # 直接用 localhost
- OPENAI_API_KEY=你前面配置的hermes的api key
- WEBUI_SECRET_KEY=your-webui-secret
restart: always
volumes:
open-webui:
启动 Open WebUI
docker compose up -d
查看日志:
docker compose logs -f open-webui
如果日志正常滚动,并最终启动完成(需要一段时间),浏览器访问: http://localhost:3000。端口改为自己设定的那个。
首次注册的用户会成为管理员。进入后台后,你就可以在连接管理中看到对应的 OpenAI 兼容连接入口。
在 Open WebUI 里配置 Hermes 连接
如果环境变量已经正确注入,有时它会自动识别;但更稳妥的做法,还是在后台手动检查一遍。
进入:模型,选择连接,到设置部分。在外部连接里面,有 OpenAI 接口 选项,可以选择添加(一般已经自动添加好了)
填写:
URL:
http://host.docker.internal:8642/v1API Key:
自定义的api key
如果验证通过,模型列表里通常会出现 Hermes 对应的模型入口或 agent 标识。
把 Hermes 做成 systemd 服务
先找到真实用户名和 hermes 路径:
whoami
which hermes
用 systemd 管理 Hermes gateway,避免手动启动:
sudo vim /etc/systemd/system/hermes.service
假设用户名分别是 name和 path,填入:
[Unit]
Description=Hermes Agent Gateway
After=network.target
[Service]
Type=simple
User=name # 改为你的用户名
ExecStart=path gateway # 改为你的hermes路径
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
在终端输入:
sudo systemctl daemon-reload
sudo systemctl enable hermes
sudo systemctl restart hermes
sudo systemctl status hermes
这样 Hermes 会随系统自动启动,Open WebUI Docker 容器也设置了 restart: always,两者重启后会自动恢复。
性能监控
# 监控容器资源使用
docker stats
# 查看特定容器日志
docker logs -f openwebui
docker logs -f hermes-agent
# 检查系统资源
top
htop
若出现问题:Open WebUI 里“验证连接”提示网络问题,模型列表为空
在 Open WebUI 后台连接配置里:
URL 填了
http://host.docker.internal:8642/v1Key 也填对了
但点击验证时提示网络问题
或者验证过不去
或者模型列表一直是空的
检查一:宿主机上的 Hermes 是否活着
curl http://localhost:8642/health
如果返回:{“status”: “ok”, “platform”: “hermes-agent”}说明 Hermes 没问题。
检查二:从容器里能不能访问宿主机 Hermes
docker exec open-webui curl -s http://host.docker.internal:8642/health
如果这里没输出,或者访问失败,那么问题就不在 Open WebUI 表单,而在 Docker 容器到宿主机 Hermes 的网络路径 上。
这个问题的根因:容器和宿主机不是一个 localhost。
在宿主机里,
localhost指的是 Ubuntu 自己。在容器里,
localhost指的是容器自己。
所以如果 Hermes 绑定在:API_SERVER_HOST=127.0.0.1
那就等于说:
“我只接受我自己回环地址的连接。”
宿主机自己访问当然可以,但 Docker 容器来的请求就不一定进得来。
这个网络问题的三种解决方案
方案一:把 Hermes 监听地址改成 0.0.0.0(最推荐)
把:API_SERVER_HOST=127.0.0.1
改成:API_SERVER_HOST=0.0.0.0
然后重启:
hermes gateway
Open WebUI 这边保持:http://host.docker.internal:8642/v1
不变即可。
优点:
逻辑最清楚。
不需要改 Open WebUI 访问方式。
对未来扩展也更友好。
缺点:
暴露面比
127.0.0.1大,所以一定要设置强一点的API_SERVER_KEY。如果你服务器有公网暴露,还需要配防火墙策略。
方案二:改用 Docker 网桥 IP 访问宿主机
先查看 Docker 网桥地址:
ip route | grep docker0
很多机器会显示:
172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1
然后把 Open WebUI 中配置的 URL 改成:
http://172.17.0.1:8642/v1
不用 host.docker.internal 这个别名,直接走 Docker 默认桥接网络的宿主机地址。
优点:
简单直接。
某些
host.docker.internal解析不稳定的环境下很好用。
缺点:
IP 依赖环境,不够通用。
如果 Docker 网桥被改过,地址可能不是
172.17.0.1。如果 Hermes 还是绑定
127.0.0.1,这个方案也可能失败。
方案三:让 Open WebUI 直接使用 host 网络模式
修改 Compose:
services:
open-webui:
image: ghcr.io/open-webui/open-webui:main
container_name: open-webui
network_mode: host
volumes:
- open-webui:/app/backend/data
environment:
- OPENAI_API_BASE_URL=http://localhost:8642/v1
- OPENAI_API_KEY=自定义的hermes api key
- WEBUI_SECRET_KEY=自定义的webui-secret
restart: always
volumes:
open-webui:
注意:
用了
network_mode: host后,要删掉ports:一般也不再需要
extra_hosts:此时 Open WebUI 自己会直接占用宿主机端口,默认访问通常是
http://localhost:8080
原理:既然容器网络绕来绕去总出问题,那就不隔离了,直接让容器共享宿主机网络栈。
优点
最简单粗暴,成功率很高。
完全避开 bridge 网络和 hostname 解析问题。
缺点
容器网络隔离减弱。
端口冲突更直接。
对初学者来说,后面排查“哪个服务占了哪个端口”会稍微乱一点。