Octop 自托管部署:Docker Compose 多用户 AI 助手
基于 Octop v0.9.19 标签在 VPS 上部署多用户 AI 助手,配置用户隔离、OpenAI 兼容模型后端和 TLS,并说明为何跳过 curl 安装脚本。
Octop 是什么,以及为什么要自行托管
Octop 是面向家庭或小型团队的自托管 AI 助手。与使用普通聊天前端相比,自行托管 Octop 的原因在于,它可以隔离不同用户。Open WebUI 为模型提供浏览器界面。Octop 则增加了带有 admin 角色的账户、每个用户独立的私有工作区和凭据集,以及一个专业 agent 库。每个用户都可以根据任务在这些 agent 之间切换。这样,一台 VPS 就可以服务 5 个人,而不只服务 1 个人。
项目位于 github.com/TencentCloud/Octop。它是一个单进程应用,可提供 Web 控制面板、命令行界面、聊天渠道(Feishu、DingTalk、QQ、Discord、WeCom)和定时任务。所有数据都存储在 ~/.octop/ 下的一个 SQLite 数据库中。以下内容均基于 v0.9.19 标签,该版本于 2026 年 8 月 5 日发布。如果您仍在比较不同平台,可在 VPS 上运行的 Open WebUI 替代方案比较涵盖了更多选项。
在您花费一晚配置之前,有一点需要明确。Octop 仍是 1.0 之前的软件,由某个厂商的 GitHub 组织发布。截至 2026 年 8 月,它大约有 900 个 star。项目迭代很快,版本号已经说明了这一点。本文不保证升级路径稳定。请固定使用某个标签,阅读变更日志,并保留备份。
开始前的准备
- 一台运行 Ubuntu 24.04 的 VPS,已安装 Docker Engine 和 Compose 插件。不熟悉 Compose?请先阅读 VPS 的 Docker Compose 基础知识。
git,因为您将检出 release tag,而不是拉取镜像。- 一个指向该 VPS 的域名,因为您需要在其前面配置 TLS(传输层安全)。
- 一个支持 OpenAI API 的模型后端:本地 Ollama、自托管网关或付费 API key。
Octop 本身资源占用很少。它由一个 Python 进程和一个 SQLite 文件组成。主要资源消耗来自模型后端。如果您计划在同一台服务器上运行模型,请根据模型的需求选择服务器规格。
不建议使用 curl 安装脚本
README 开头提供了一条单行安装命令:
curl -fsSL https://finnie-1258344699.cos.ap-guangzhou.myqcloud.com/octop/install.sh | bash对于需要维护的服务器,我们不建议使用这种方式,原因很明确:该脚本不在代码仓库中,而是由 Tencent Cloud Object Storage 存储桶提供。它没有受到 git 标签或提交记录的约束,因此您无法比较今天的脚本与上周的版本,也没有历史记录说明脚本为何发生变化。存储桶明天可以返回不同的内容,而项目中不会留下任何记录。将结果直接通过管道传给 bash,还意味着机器会在您阅读脚本之前执行它。
该安装程序还会直接修改主机,而不是运行在容器中。它使用 uv 获取 Python 3.12,并创建一个包管理器完全不了解的环境,因此之后需要手动清理。
有两个更好的选择。先获取脚本并阅读,然后再运行。这样只需花费约三十秒:先执行 curl -fsSL <url> -o install.sh,再执行 less install.sh,最后执行 bash install.sh。或者使用 Docker,这也是本指南后续采用的方式。PyPI 软件包(pip install octop)至少是一个带版本号的构件,您可以将其固定到某个发布版本。
使用 Docker Compose 部署 Octop,并固定到 v0.9.19
截至 August 2026,没有可供拉取的已发布镜像。随附的 Compose 文件会从仓库构建镜像,因此固定版本意味着检出对应的 git tag。
git clone https://github.com/TencentCloud/Octop.git
cd Octop
git checkout v0.9.19这是该文件定义的服务,下面仅保留相关部分:
services:
octop:
build:
context: ..
dockerfile: docker/Dockerfile
image: octop:latest
container_name: octop
restart: unless-stopped
ports:
- "${OCTOP_PORT:-8088}:${OCTOP_PORT:-8088}"
volumes:
- ${OCTOP_DATA:-~/.octop}:/data/.octop
environment:
- HOME=/data
- OCTOP_BIND_HOST=0.0.0.0
- OCTOP_PORT=${OCTOP_PORT:-8088}
- OCTOP_DEFAULT_PASSWORD=${OCTOP_DEFAULT_PASSWORD:-octop}
- OCTOP_ADMIN_USERNAME=${OCTOP_ADMIN_USERNAME:-admin}
- OPENAI_API_KEY=${OPENAI_API_KEY:-}注意 build: 块。image: octop:latest 是本地构建生成的镜像名称,不是 registry 引用,因此这里的 latest 表示最近一次编译的内容。将数据路径设置为明确的目录,不要使用默认路径;并在首次启动前为管理员账户设置真实密码。将以下内容写入 docker/.env:
OCTOP_PORT=8088
OCTOP_ADMIN_USERNAME=admin
OCTOP_DEFAULT_PASSWORD=<a long random password>
OCTOP_DATA=/srv/octop-data这里有一个问题比文件中的其他部分更容易导致误解。Compose 读取 docker/.env,只用于替换 YAML 中的 ${...} 占位符。添加到该文件的键,除非同时列在 Compose 文件的 environment: 下,否则不会传递到容器中。只将 OCTOP_ACCESS_TOKEN_TTL 添加到 .env 不会产生任何效果,而且不会显示错误。另一种方法是将相同的键写入挂载数据目录中的 ~/.octop/env,Octop 会在启动时加载该文件。Docker Compose 中环境变量文件和密钥的指南详细说明了这两种机制为何不同。
构建并启动:
docker compose -f docker/docker-compose.yml up -d --build
docker compose -f docker/docker-compose.yml ps
curl http://127.0.0.1:8088/api/health运行正常的实例会使用 {"status":"ok","version":"..."} 响应健康检查。若返回其他内容,请先读取 docker compose -f docker/docker-compose.yml logs -f octop,不要立即检查浏览器。
现在为刚构建的镜像设置一个有意义的名称,因为下一次 --build 会覆盖 octop:latest,届时将无法区分这两个镜像:
docker image tag octop:latest octop:0.9.19首次启动会运行 octop init,并将初始凭据写入数据卷:
docker exec -it octop cat /data/.octop/credential.txt默认值为 admin / octop,并且仅在首次初始化时应用。这也是一个经常被问到的问题的原因:容器启动过一次后,再修改 OCTOP_DEFAULT_PASSWORD 不会产生任何变化,因为账户已经创建。请改用管理面板修改密码。
不要发布端口 8088
上面的 ports: 行会在 VPS 的所有网络接口上监听。容器一启动,仪表板就会以明文形式暴露在公网中,并使用默认密码。Octop 自身的 OCTOP_BIND_HOST 默认值是 127.0.0.1;Compose 文件将其覆盖为 0.0.0.0,因为该进程必须接收来自自身网络命名空间之外的流量。这个覆盖设置是正确的。真正暴露服务的是发布端口。
编辑 docker/docker-compose.yml 中的 ports: 行,使映射仅在回环接口上监听:
ports:
- "127.0.0.1:${OCTOP_PORT:-8088}:${OCTOP_PORT:-8088}"不要尝试使用普通的覆盖文件修复此问题。Compose 会合并多个文件中的 ports 列表,而不是替换它们,因此最终会同时发布两个映射,第二个映射会绑定失败。如果希望保留上游文件不变,请在该序列上使用 !override 标签。这是文档规定的替换而不是追加的方法。Compose 合并多个文件的说明介绍了其余合并规则。
绑定到回环接口还可以解决防火墙方面的问题。Docker 会将发布端口规则写入 nat 表,并置于 ufw 管理的链之前,因此 ufw deny 8088 无法阻止已发布的容器端口。绑定到 127.0.0.1 的端口无论 ufw 如何配置,都无法从外部访问。因此,这是正确的修复方式,而不是次优方案。
在前端使用反向代理处理 TLS
Caddy 是最简便的方案,因为它会自行通过 ACME(自动证书管理环境)申请证书,并且无需额外配置即可代理 WebSocket:
octop.example.com {
reverse_proxy 127.0.0.1:8088
}nginx 需要更谨慎地配置,因为 Octop 通过 WebSocket 传输聊天数据:
server {
listen 443 ssl;
server_name octop.example.com;
ssl_certificate /etc/letsencrypt/live/octop.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/octop.example.com/privkey.pem;
location / {
proxy_pass http://127.0.0.1:8088;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_buffering off;
proxy_read_timeout 3600s;
}
}这里的每一行配置都有作用。聊天通过 WS /agents/{id}/chat/ws 运行,因此如果缺少 proxy_http_version 1.1 和两个升级标头,nginx 会以 400 Bad Request 响应升级请求:控制面板可以正常加载,但您发送的每条消息都会永久挂起,页面上不会显示错误。proxy_buffering off 很重要,因为人工参与恢复端点会返回 text/event-stream;如果 SSE(服务器发送事件)被代理缓冲,所有事件都会在最后一次性到达,而不是持续流式传输。proxy_read_timeout 用于覆盖长时间运行的工具调用,因为默认的 60 秒超时会在代理任务执行到一半时中断,并在日志中记录 upstream timed out (110: Connection timed out)。
代理后 JWT 认证的工作方式
Octop 使用 bearer token 进行身份验证,而不是使用 cookie。POST /api/auth/login 返回 {access_token, role, user, ...},后续请求携带 Authorization: Bearer <access_token>。对于反向代理来说,这是好消息:不需要处理 cookie domain、Secure 标志或 SameSite 规则,因此在 http://127.0.0.1:8088 上正常工作的会话,在 https://octop.example.com 上的行为也相同。
在让实际用户使用之前,需要了解以下两点。
WebSocket 会在 URL 中携带 token。 端点为 WS /agents/{id}/chat/ws?token=<jwt>,因为浏览器 JavaScript 无法在 WebSocket 握手中设置 Authorization header。TLS 可以保护 token 在传输过程中的安全,但无法防止 token 出现在您自己的日志中:nginx 默认会将完整请求行(包括查询字符串)写入 access_log,因此实际用户正在使用的 token 会出现在服务器上的明文文件中。请记录不含参数的路径。$uri 是已去除查询字符串的规范化路径,因此请将以下配置放入 http 块中,并在 server 中引用它:
log_format octop_noargs '$remote_addr [$time_local] '
'"$request_method $uri $server_protocol" '
'$status $body_bytes_sent';
access_log /var/log/nginx/octop.log octop_noargs;不支持按会话注销。 OCTOP_ACCESS_TOKEN_TTL 默认为 86400,因此 token 在登录后 24 小时内保持有效。唯一有文档说明的失效方式是 octop admin rotate-jwt-secret。该操作会轮换存储在 ~/.octop/secrets/jwt_secret 中的签名密钥,并立即使所有尚未过期的 token 失效。这个操作会影响所有用户。因此,当有人离开团队时,操作顺序是:删除用户、轮换密钥,然后通知其他用户重新登录。如果这项操作开销过大,可以缩短有效期,但请记得同时将该变量添加到 environment: 列表和 .env 中:
OCTOP_ACCESS_TOKEN_TTL=28800系统会处理暴力破解:OCTOP_LOGIN_MAX_ATTEMPTS 默认为 5 次失败,OCTOP_LOGIN_LOCKOUT_SECONDS 默认为 900,因此被锁定的用户只需等待 15 分钟,而不必误以为安装已损坏。Octop 使用自己的用户存储,并且在 v0.9.19 中没有文档说明的 OIDC 支持。因此,如果需要真正的单点登录,应在其前面部署身份验证代理,这正是 自托管的 Authentik 服务器 的用途。
为 Octop 指定模型后端
在 dashboard 中按 agent 配置 provider,octop provider list 会显示当前设置。Octop 提供 OpenAI-compatible API、DashScope (Qwen) 和 Ollama 的预设,凭据存储在您自己的 SQLite 数据库的 providers 表中。此选择会影响费用,以及哪些数据会离开服务器。
使用 Ollama 的本地模型。 数据不会离开服务器,您消耗的是 RAM,而不是 token。一个容易出问题的连接细节是:容器无法访问主机的 Ollama 127.0.0.1:11434,因为该地址指向容器自身的 loopback。为该服务添加 host gateway:
extra_hosts:
- "host.docker.internal:host-gateway"然后将 provider base URL 设置为 http://host.docker.internal:11434/v1,这是 Ollama 的 OpenAI-compatible 路径。API key 字段中填入任意非空字符串,因为 Ollama 会忽略该值,但 OpenAI 客户端拒绝发送空值。Ollama 还必须监听 loopback 之外的地址才能正常工作,这意味着需要在其 systemd unit 中设置 OLLAMA_HOST=0.0.0.0:11434。这也是风险所在:Ollama 没有身份验证,因此公共 IP 上开放的 11434 端口会成为任何扫描到它的人都能免费使用的模型服务器。仅允许 Docker 的私有网段 sudo ufw allow from 172.16.0.0/12 to any port 11434 proto tcp,拒绝其他来源。在 VPS 上运行 Ollama介绍模型规格选择,Ollama 与 vLLM 的比较介绍 Ollama 何时不再适合作为服务器。
还需要说明一个本地模型相关的警告,因为它看起来像 Octop 的 bug,但实际并不是。Agent 通过调用工具工作,system prompt、工具定义和历史记录会组成很大的 prompt。Ollama 提供的模型默认上下文窗口较小,因此 prompt 开头的内容会超出窗口,而工具定义正位于开头。此后模型会停止调用工具,或虚构不存在的工具。将 num_ctx 提高到 16k 或 32k,并选择实际擅长 function calling 的模型。
使用自托管 gateway。 在 Octop 与其他所有服务之间部署自托管 LiteLLM gateway,即可使用一个 base URL、每个用户独立的 key、费用上限和单一日志。您还可以替换 gateway 后面的模型,而无需修改 Octop 中的任何内容。
使用付费 API。 质量通常最高,但需要接受明确的取舍:对话内容会离开服务器并发送给 provider,而这正是自托管所要避免的主要问题。将 key 作为 OPENAI_API_KEY 放入 docker/.env,Compose 文件已经会将其传递进去。
无论选择哪种方式,Compose 文件还会传递 OCTOP_LANGFUSE_ENABLED、LANGFUSE_PUBLIC_KEY、LANGFUSE_SECRET_KEY 和 LANGFUSE_BASE_URL,因此您可以将 trace 发送到自己的 Langfuse 实例,直接查看 agent 实际执行的操作,而不是仅根据 chat 窗口猜测。
用户、角色和共享代理库
首次启动时创建的管理员账户负责创建和管理其他账户。每个用户都有自己的代理、工作区和凭据,这种隔离由浏览器持有的令牌实现。同时,系统还提供一个共享的技能和子代理池,所有人都可以使用。正是这一点使它适合家庭使用:一个人构建好研究代理后,其他人无需重复构建。
请谨慎使用相关工具。Octop 提供工具审批和 Shell 命令防护,这两项功能确实有效。但运行 Shell 命令的代理会在 Octop 容器内执行命令,而您的数据卷已挂载到该容器中。防护机制可以限制不谨慎的提示词造成的影响,但不能构成沙箱边界。因此,除非您愿意直接向相关人员提供 Shell 访问权限,否则应为其启用工具审批。如果您正在将它与其他选项进行比较,自托管 AI 代理对比介绍了各个选项在这方面的处理方式。
升级如此频繁发布的项目
The data behind this chart
[
{
"version": "v0.9.16",
"days_since_previous_release": 2
},
{
"version": "v0.9.17",
"days_since_previous_release": 3
},
{
"version": "v0.9.18",
"days_since_previous_release": 1
},
{
"version": "v0.9.19",
"days_since_previous_release": 3
}
]以下是仓库中的标签日期,统计截至 7 August 2026。9 天内发布了 4 个带标签的版本,最短间隔仅为 1 天;v0.9.19 在前一个标签发布 3 天后发布。这种发布频率说明项目维护活跃,但不适合直接运行 latest。执行升级前,先阅读变更内容:
cd Octop
git fetch --tags
git tag --sort=-creatordate | head
NEW_TAG=$(git tag --sort=-creatordate | head -1)
git log --oneline "v0.9.19..$NEW_TAG"每次都先备份,因为数据库迁移会在启动时运行;对于 1.0 之前的项目,迁移失败后需要由您自行处理:
docker compose -f docker/docker-compose.yml stop
sudo tar czf octop-backup-$(date +%F).tgz -C /srv octop-data
docker compose -f docker/docker-compose.yml start然后切换到新的标签,并使用 docker compose -f docker/docker-compose.yml up -d --build 重新构建。如果出现问题,切换回旧标签并重新构建可以恢复代码,但只有 tarball 才能恢复数据库。
该 tarball 包含 octop.db、config.json、JWT 签名密钥和 credential.txt,因此其敏感程度与服务器本身相同。将其权限设为 600,并在服务器之外保留一份副本。对于更大规模的部署,项目还提供 docker/docker-compose.postgres.yml,它使用带有 pgvector 的 PostgreSQL,而不是 SQLite。
故障现象及对应提示
健康检查始终无响应。 curl http://127.0.0.1:8088/api/health 一直挂起或拒绝连接。查看 docker compose -f docker/docker-compose.yml logs -f octop。容器在首次初始化期间退出,通常是因为无法写入数据目录,因此请检查您为 OCTOP_DATA 设置的路径对应的所有权。
控制面板可以加载,但聊天一直挂起。 页面没有错误,也始终没有回复。打开浏览器控制台,查找连接 wss://octop.example.com/agents/.../chat/ws 失败的记录。代理未转发升级请求。添加 proxy_http_version 1.1 以及 Upgrade 和 Connection 标头。
整条回复会在几秒后一次性显示。 流式传输正常,但启用了缓冲。设置 proxy_buffering off。
bind: address already in use。 已有其他进程占用 8088。sudo ss -tlnp | grep 8088 可显示占用该端口的进程。这也可能是因为您在 override 文件中添加了第二个 ports 条目,而不是编辑原有条目。
输入正确密码仍被拒绝。 连续 5 次输入错误会触发 900 秒锁定。请等待锁定结束,不要重新安装。
.env 中的新密码没有生效。 这些凭据仅在首次初始化时生效。请在控制面板中修改密码。
代理可以回复,但始终不会运行工具。 这几乎总是本地模型问题:上下文窗口太小,无法容纳工具定义,或者模型不擅长函数调用。调高 num_ctx,并尝试使用针对工具调用构建的模型。
FAQ
Octop 是 Open WebUI 的替代品吗?
只有在您需要 Octop 提供的功能时才是。Open WebUI 是模型前的聊天界面,为个人或相互信任的家庭提供聊天功能,而且表现良好。Octop 增加了带管理员角色的账户、按用户划分的工作区和凭据,以及可切换的专业代理库,因此多人可以共用一台服务器,而不必共用同一份历史记录。如果您只需要一个账户,Open WebUI 更简单,也成熟得多。
为什么不应使用 Octop 的 curl 安装脚本?
该脚本托管在 Tencent Cloud Object Storage 存储桶中,而不是代码仓库中,因此不受任何 git 标签或提交记录约束。您无法比较它今天的行为与上周的行为,而且将其通过管道传给 bash 会在您阅读之前直接执行。该脚本还会在主机上安装自带的 Python 3.12 环境,不受软件包管理器管理。请先下载并阅读脚本,或者从已检出的标签使用 Docker Compose 部署。
Octop 可以使用本地模型,而不是付费 API 吗?
可以。Octop 支持 OpenAI 兼容 API,并提供 Ollama 预设,因此在容器中添加 extra_hosts: ["host.docker.internal:host-gateway"] 并在主机上设置 OLLAMA_HOST=0.0.0.0:11434 后,指向 http://host.docker.internal:11434/v1 即可使用。请将防火墙中的 11434 端口限制为 Docker 的地址范围,因为 Ollama 本身不提供身份验证。建议将 Ollama 的 num_ctx 调高到 16k 或更高,因为包含工具定义的代理提示会超出默认上下文窗口,随后模型将停止调用工具。
我需要反向代理,还是可以直接开放 8088 端口?
您需要使用反向代理。Octop 提供的 Compose 文件会在所有网络接口上发布 8088 端口,且未启用 TLS,因此密码和 bearer token 会以明文形式通过互联网传输。请将发布端口改为 127.0.0.1:8088:8088,并在前面配置 Caddy 或 nginx 以及证书。使用 nginx 时,请转发 WebSocket 升级请求头并设置 proxy_buffering off,否则页面可以加载,但聊天不会响应,而且通常没有明显提示。
Octop 已经可以用于生产环境了吗?
截至 August 2026,Octop 仍处于 1.0 之前的版本阶段,每周会发布多个带标签的版本,因此应将其视为有潜力的软件,而不是已经稳定的软件。如果您固定使用确切的标签,在每次升级前阅读提交日志,并在每次重建前备份数据卷,那么它适合家庭或小型内部团队使用。不要在 latest 上运行它,也不要在其中存放客户数据。