Octop 自托管部署:Docker Compose 多用户 AI 助手
以 v0.9.19 标签在 VPS 上部署 Octop,配置 Docker Compose、用户隔离、OpenAI 兼容模型后端和 TLS,并说明为何不应使用 curl 安装脚本。
Octop 是什么,以及为什么要自行托管
Octop 是面向家庭或小型团队的自托管 AI 助手。与使用普通聊天前端相比,自行托管 Octop 的原因在于,它可以隔离不同用户。Open WebUI 提供位于模型前端的浏览器界面。Octop 在此基础上增加了管理员角色、每个用户独立的私有工作区和凭据集,以及一个专业代理库。每个用户都可以根据任务切换代理。这项差异使一台 VPS 可以服务五个人,而不是只能服务一个人。
项目位于 github.com/TencentCloud/Octop。它以单个进程运行,提供 Web 控制面板、命令行界面、聊天渠道(Feishu、DingTalk、QQ、Discord、WeCom)和计划任务,所有数据都存储在 ~/.octop/ 下的单个 SQLite 数据库中。以下内容均以 v0.9.19 标签为准,该版本于 5 August 2026 发布。如果您仍在选择平台,可参考可在 VPS 上运行的 Open WebUI 替代方案对比,了解更广泛的选择。
在您花费一晚进行部署前,有一点需要明确。Octop 仍是 1.0 之前的软件,由某个厂商的 GitHub 组织发布;截至 August 2026,它约有 900 个 stars。项目迭代很快,版本号已经说明了这一点,这里的内容也不代表它提供稳定的升级路径。请固定使用某个标签,阅读变更日志,并保留备份。
开始前的准备工作
- 一台运行 Ubuntu 24.04、已安装 Docker Engine 和 Compose 插件的 VPS。不熟悉 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 tag 或提交记录的保护,因此您无法比较今天的脚本与上周的脚本,也没有历史记录说明脚本为何发生变化。存储桶明天可以返回不同的内容,而项目中不会留下任何记录。将结果直接通过管道传给 bash,还意味着机器会在您查看脚本内容之前执行它。
该安装程序还会直接写入主机,而不是写入容器。它使用 uv 获取 Python 3.12,并构建一个包管理器完全不了解的环境,因此之后删除它只能手动处理。
有两个更好的选择。先获取脚本并阅读,然后再运行。整个过程只需约三十秒:先执行 curl -fsSL <url> -o install.sh,再执行 less install.sh,最后执行 bash install.sh。或者使用 Docker,这也是本指南后续采用的方式。PyPI 软件包(pip install octop)至少是一个带版本的构件,您可以将其固定到某个 release。
使用 Docker Compose 部署 Octop,并固定到 v0.9.19
截至 August 2026,没有可供拉取的已发布镜像。随附的 Compose 文件会从代码仓库构建镜像,因此固定版本意味着检出 git tag。这比大多数自托管项目多一个步骤,因为例如自托管 AFFiNE 工作区会固定到已发布的镜像 tag,并且完全不会在 VPS 上构建镜像。下面的克隆、检出和构建流程与openGym 部署指南中的流程相同,因此如果您已经完成过一次配置,就已经了解其基本结构。
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: 行,使该映射只监听 loopback:
ports:
- "127.0.0.1:${OCTOP_PORT:-8088}:${OCTOP_PORT:-8088}"不要尝试使用普通的 override 文件修复此问题。Compose 会合并多个文件中的 ports 列表,而不是替换它们,因此最终会发布两个映射,第二个映射会绑定失败。如果您希望保持上游文件不变,请在该序列上使用 !override 标签。这是文档规定的替换而非追加方式。Compose 合并多个文件的说明介绍了其余合并规则。
绑定到 loopback 还可以解决防火墙方面的问题。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 在传输过程中的安全,但无法防止它出现在您自己的日志中: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 失效。也就是说,当有人离开团队时,应按以下顺序操作:删除用户、轮换 secret,然后通知其他用户重新登录。如果这项操作过于繁琐,可以缩短有效期,但同时要将该变量加入 environment: 列表和 .env:
OCTOP_ACCESS_TOKEN_TTL=28800系统会处理暴力破解:OCTOP_LOGIN_MAX_ATTEMPTS 默认为 5 次失败,OCTOP_LOGIN_LOCKOUT_SECONDS 默认为 900,因此被锁定的用户只需等待十五分钟,而不是误以为安装已损坏。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。容易出错的连接细节是:容器无法通过 127.0.0.1:11434 访问宿主机上的 Ollama,因为该地址指向容器自身的 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 通过调用工具工作,系统提示词、工具定义和历史记录合起来会形成很长的 prompt。Ollama 为模型提供的默认上下文窗口通常较小,因此 prompt 开头的内容会超出窗口,而工具定义就在开头。模型随后会停止调用工具,或自行编造不存在的工具。请将 num_ctx 调高到 16k 或 32k,并选择确实擅长 function calling 的模型。回复在句子中途停止是另一类问题,对应不同的设置 num_predict;因此,如果回答被截断,建议先检查 num_predict 的设置位置以及 done_reason 的内容,再判断是否是 agent 的问题。如果您不想从候选列表开始,而是希望直接尝试某个特定模型,Nemotron 3.5 Lightning值得一试。该文章会给出准确的 tag、所需 RAM,以及仅使用 CPU 时是否能够保持足够速度。
使用自托管 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 window 猜测。
用户、角色与共享智能体库
首次启动时创建的管理员账户负责创建和管理其他账户。每个用户都有自己的智能体、工作区和凭据,这种隔离由浏览器持有的令牌实现。同时,系统还提供所有用户都可使用的共享技能和子智能体池。这正是它适合家庭部署的原因:一个人只需构建一次高质量的研究智能体,其他人无需重复配置。
请谨慎使用工具。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。4 个带标签的版本在 9 天内发布,最短间隔仅为 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"每次都先备份,因为数据库迁移会在启动时运行,而在 pre-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,它使用 PostgreSQL 和 pgvector,而不是 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 吗?
只有在您需要它新增的功能时才可以。Open WebUI 是模型前的聊天界面。对于单人或彼此信任的家庭,它能很好地完成这项工作。Octop 增加了带管理员角色的账户、按用户隔离的工作区和凭据,以及可切换的专业代理库。因此,多人可以共享一台服务器,而不必共享同一份历史记录。如果您可以接受使用单个账户,Open WebUI 更简单,也成熟得多。
为什么不应使用 Octop 的 curl 安装脚本?
该脚本托管在 Tencent Cloud Object Storage 存储桶中,而不是代码仓库中,因此不受任何 git 标记或提交记录约束。您无法比较它今天的行为与上周的行为,而且将脚本通过管道传给 bash 会在您阅读之前直接执行。该脚本还会使用自己的 Python 3.12 环境安装到主机上,绕过您的包管理器。请先下载并阅读脚本,或从已检出的标记使用 Docker Compose 部署。
Octop 可以使用本地模型,而不是付费 API 吗?
可以。Octop 支持 OpenAI 兼容 API,并提供 Ollama 预设。因此,将其指向 http://host.docker.internal:11434/v1 后,只要将 extra_hosts: ["host.docker.internal:host-gateway"] 添加到容器中,并在主机上设置 OLLAMA_HOST=0.0.0.0:11434,即可使用。请将防火墙端口 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 上运行它,也不要暂时将客户数据放入其中。