OneCLI 自托管部署:每人一个沙箱代理
了解如何用 Docker Compose、PostgreSQL 和 VPS 自托管 OneCLI,为每个人运行独立沙箱代理,并由单一网关保管 API 密钥。注意每个代理默认需要 2 GiB 内存,1 GB VPS 不适用。
自行托管 OneCLI 后的架构
自行托管 OneCLI 后,团队中的每个人都可以拥有自己的代理。每个代理都运行在独立的沙箱中。API 密钥由网关保管,代理无法读取这些密钥。安装方式是 Docker Compose 堆栈,后端使用 PostgreSQL,可通过 http://localhost:10254 访问。请准备一台实际的服务器。文档规定每个代理沙箱默认需要 2 GiB 内存,因此 1 GB VPS 不适合运行此工作负载。
该堆栈包含七个组件。了解各组件的职责后,后续内容会更容易理解。
- Web 仪表板(Next.js),端口为
10254。用于创建代理、聊天、编辑记忆和技能、管理连接与密钥。 - API 服务器,端口为
10256。这是控制平面,负责数据库、会话处理和工作队列。 - Rust 网关,端口为
10255。拦截代理发出的出站请求,并注入凭据。 - Runner。README 将其描述为负责“启动、暂停和回收代理沙箱”的组件。它仅处理出站流量,并且不会访问数据库。
- Sandbox Supervisor。README 将其描述为“在每个沙箱内部运行,通过与供应商无关的 harness 接口通信,因此可以替换代理运行时”。
- Channel adapter。这是连接 Slack 应用的守护进程,使代理可以使用自己的名称回复频道消息和私信。
- PostgreSQL。随附的 compose 文件使用
postgres:18-alpine,并挂载pgdata卷。
名称是 CLI,但产品是服务器
OneCLI 是一个服务器平台。这个名称容易让人以为它是安装在笔记本电脑上的命令行工具,但本指南介绍的实际产品并非如此。onecli/onecli-cli仓库中确实提供了一个独立的命令行客户端,它会将本地编码代理的流量通过网关路由。这里要部署的是一个多用户 Web 应用:包含账户系统,其中第一个账户拥有整个实例;包含用于存储对话和密钥的数据库;还包含用于启动容器的运行器。
按人员划分代理是整个设计的核心。README 中写道:“为每个人创建一个代理,为每个代理授予所需的访问权限;代理在沙箱中运行,并通过注入凭据且强制执行策略的网关进行路由。”每个代理都有自己的文件系统和 shell、独立的对话页面、由平台保存的记忆,以及可一次编写并重复使用的技能。凭据的使用方式与常见配置相反。不要将 API key 复制到每个人的环境中,而是只存储一次,然后将其授予允许使用它的代理。
开始前服务器需要满足的条件
- Docker,并安装 2.19 或更高版本的 Compose 插件。Compose 文件使用一次性迁移服务,API 会等待该服务完成;这种依赖形式需要 2.19。
- 内存,这是实际的限制因素。选择方案前,请先阅读下面的容量规划部分。
- 空闲的回环端口
10254、10255、10256和5432。
您不需要自行安装 PostgreSQL:Compose 文件会将其作为服务运行。您也不需要安装 Node.js 或 Rust。这些工具仅用于从源代码构建的路径,其中 mise 会固定工具链版本。
VPS 可容纳多少个 agent 沙箱?
Runner 的文档提供了具体数值,而不是凭感觉估算。每个沙箱分配 2048 MB 内存(RUNNER_SANDBOX_MEMORY_MB)、1 个 CPU(RUNNER_SANDBOX_CPUS)和 512 个进程(RUNNER_SANDBOX_PIDS)。并发上限为 4(RUNNER_MAX_SANDBOXES)。文档建议,除基础服务栈外,额外预留约 10 GiB 可用内存,以满足该并发上限。
The data behind this chart
[
{
"plan": "2 GB box",
"ram_gb": 2,
"sandbox_slots": 0
},
{
"plan": "4 GB box",
"ram_gb": 4,
"sandbox_slots": 1
},
{
"plan": "8 GB box",
"ram_gb": 8,
"sandbox_slots": 3
},
{
"plan": "16 GB box",
"ram_gb": 16,
"sandbox_slots": 7
},
{
"plan": "32 GB box",
"ram_gb": 32,
"sandbox_slots": 15
}
]这些槽位数量是算术结果,不是基准测试结果:总内存减去 PostgreSQL 和 4 个长期运行服务约 2 GB 的占用,再除以沙箱 2 GiB 上限。按此计算,2 GB box 可容纳 0 个沙箱,因此最便宜的方案根本无法运行托管 agent。16 GB box 可容纳 7,明显高于默认的 4 个并发沙箱,也高于 Runner 文档要求的约 10 GiB 可用内存。32 GB box 可容纳 15 个沙箱。
有两点会影响这个计算结果。运行后台进程的沙箱不会进入休眠,因此会永久占用一个槽位。这意味着应按 RUNNER_MAX_SANDBOXES 的持续负载进行容量规划,而不是按最繁忙的一分钟计算。另一个限制是内存会先于 CPU 耗尽。每个沙箱最多使用 1 个 CPU,因此 4 个繁忙的 agent 需要 4 个核心;但 4 个处于空闲状态却仍保持运行的 agent 仍会占用 8 GiB 内存。
可能需要调整的 Runner 设置
RUNNER_MAX_SANDBOXES(默认值:4):同时运行的沙箱数量。RUNNER_SANDBOX_MEMORY_MB(默认值:2048):每个沙箱的内存上限。RUNNER_SANDBOX_CPUS(默认值:1):每个沙箱的 CPU 上限。RUNNER_SANDBOX_PIDS(默认值:512):每个沙箱的进程数上限。RUNNER_NETWORK_INTERNAL(默认值:true):使沙箱网络无法访问外部路由。保持启用。RUNNER_SANDBOX_NETWORK(默认值:onecli-sandboxes):沙箱加入的网络。RUNNER_RECONCILE_SECONDS(默认值:60):Runner 协调状态的频率。RUNNER_ORPHAN_GRACE_SECONDS(默认值:3600):销毁孤立容器和卷的存留时间。RUNNER_AGENT_IMAGE:覆盖沙箱镜像;否则沙箱镜像遵循ONECLI_VERSION。
使用 Docker Compose 安装 OneCLI
上游自托管文档给出了以下完整步骤。该步骤会将 3 个密钥写入 compose 文件旁边的 docker/.env,然后启动堆栈。
git clone https://github.com/onecli/onecli.git && cd onecli/docker
cat > .env <<EOF
SECRET_ENCRYPTION_KEY=$(head -c 32 /dev/urandom | base64)
GATEWAY_INTERNAL_SECRET=$(head -c 32 /dev/urandom | base64)
BETTER_AUTH_SECRET=$(head -c 32 /dev/urandom | base64)
COMPOSE_PROFILES=runner
EOF
chmod 600 .env
docker compose up -d --wait运行前先阅读这段内容。此处的 heredoc 标记未加引号,因此 shell 会执行其中的每个 head -c 32 /dev/urandom | base64,并写入执行结果,而不是写入字面文本。SECRET_ENCRYPTION_KEY 是数据库中所有密钥使用的 AES-256-GCM 密钥。GATEWAY_INTERNAL_SECRET 用于验证网关对 API 的身份。BETTER_AUTH_SECRET 用于签名会话 Cookie。COMPOSE_PROFILES=runner 最重要,因为 runner 服务位于 Compose profile 后面:如果省略它,堆栈会显示为健康,但不会启动任何 agent sandbox。
--wait 会一直等待,直到每个服务都报告健康状态。因此,非零退出状态是发现问题的第一个信号。然后检查实际启动了哪些服务。
docker compose ps
docker compose logs migrations固定版本。ONECLI_VERSION 会一次性为所有服务设置标签;除非 RUNNER_AGENT_IMAGE 指向其他位置,否则 agent sandbox 镜像也会使用该标签。截至 19 August 2026,当前版本为 v2.0.1,于 18 August 2026 发布。将它添加到同一个文件中,然后重新启动堆栈。
echo 'ONECLI_VERSION=v2.0.1' >> .env
docker compose up -d --wait此外还有安装程序 curl -fsSL https://onecli.sh/install | sh。它会将配置写入 ~/.onecli/.env,并执行相同的操作。Compose 方式允许您在任何内容运行前读取所有文件;如果服务器上已经运行其他 Compose 堆栈,应使用这种方式。第三种方式是从源代码构建,克隆的仓库中将其文档列为 pnpm install,然后是 pnpm run setup。这种方式仍需要 mise、用于网关的 Rust 和 Docker,适用于准备修改代码的用户。
从笔记本电脑访问仪表板
随附的 compose 文件中,每个发布的端口都绑定到 ${ONECLI_BIND_HOST:-127.0.0.1}。在 VPS 上,这表示仪表板正在运行,但主机外部无法访问它。这个默认设置是正确的。请保留该设置,并建立隧道:
ssh -N -L 10254:127.0.0.1:10254 you@your-server现在在笔记本电脑上打开 http://localhost:10254。流量通过 SSH 连接传输,因此公网中不会暴露未加密的仪表板,也无需为额外端口配置防火墙。
设置 ONECLI_BIND_HOST=0.0.0.0 会通过普通 HTTP 发布仪表板,同时发布 PostgreSQL。如果需要让多个人访问仪表板,请在端口 10254 前配置带 TLS(传输层安全)的反向代理,并保持绑定主机不变。应在该实例有负责人之前完成此操作。上游文档明确说明了原因:“在此之前,该实例没有负责人;任何首先访问到它的人都会成为负责人。”如果反向代理已经为其他自托管应用提供入口,则通过 自托管的单点登录层转发身份验证,可将仪表板置于团队现有的登录保护之后。这样,在一个位置移除某人的权限,也会同时关闭这条访问路径。
创建首个账户,然后授予模型密钥
打开控制面板后立即创建账户。该账户拥有此实例。账户创建后,其他人必须通过邀请加入。
然后在创建 agent 前存储模型密钥。托管 agent 需要已授予的模型密钥,操作顺序很重要:先在控制面板中存储密钥,再将其授予 agent,最后启动对话。如果跳过授予步骤,沙箱将不会启动,表现为 agent 一直没有任何操作。
请严格限制授予范围。每个 agent 只能使用授予它的权限,网关会在每个请求上强制执行这些限制。因此,只读取一个代码仓库的 agent 无法访问支付服务提供商的密钥。相同的授予列表也可用于控制支出。允许每个人的 agent 调用您拥有的任何模型,就等于为每个人单独生成一份账单。因此,在向十个人授予权限前,请先了解如何 限制 agent 在模型调用上的支出。
网关如何让密钥不进入代理程序
该网关是使用 Rust 编写的 HTTPS 代理,监听 10255 端口。代理程序的 HTTP 客户端指向网关,代理程序携带的是占位凭据,而不是真实凭据。网关根据代理程序的授权规则匹配出站请求,解密真实密钥,将其替换到请求中,然后转发请求。密钥使用 AES-256-GCM(高级加密标准、256 位、伽罗瓦/计数器模式)加密后存储在 PostgreSQL 中,并且只在请求时解密。每次调用都会记录代理程序的身份和目标。这会形成审计记录,而当密钥存放在十个人的 shell 配置文件中时,无法获得这样的记录。
部署方式取决于以下两个机制。
- HTTPS 拦截属于中间人攻击。网关生成本地证书颁发机构,代理程序信任该证书颁发机构。网关随后终止代理程序的 TLS 连接,再与上游服务建立新的 TLS 连接。因此,如果代理程序的 HTTP 客户端不信任网关的证书颁发机构,连接会因证书验证错误而失败,而不是因身份验证错误而失败。
- 代理程序通过
Proxy-Authorization标头标识自身。在单机部署中,代理程序和网关共享内部 Docker 网络,该标头不会经过您不拥有的网络。将不在同一台主机上的代理程序指向网关时,代理端口需要单独启用 TLS,因为该标头属于 bearer token。
需要明确的是:网关会按设计以明文读取代理程序发出的每个请求。它是主机上最敏感的进程。应相应保护其所在主机,并通过 最小权限 Linux 用户限制可登录该主机的人员数量。
运行器无需入站端口
运行器仅发起出站连接。其文档说明:“它不占用任何外部网络可访问的端口,因此笔记本电脑、家庭实验室或位于 NAT 后的 VPC 都无需入站连接、隧道或 TLS 终止配置即可使用。”NAT 是网络地址转换,即家用路由器执行的功能。运行器向控制平面发起连接并从中获取任务,因此无需转发任何内容,也无需开放任何端口。
这种设计在沙箱网络中发挥了作用。Compose 文件定义了第二个标记为 internal: true 的网络。在 Docker 中,这表示该网络完全无法从主机向外部路由。沙箱会加入此网络。网关同时连接两个网络,因此它是唯一的出站路径。运行器文档明确说明了这一点:“将网关同时连接到 internal 网络,才能使仅允许通过网关出站成为真正的边界,而不是一项建议。”如果代理决定将您的源代码发送到它自行选择的地址,它没有路由可执行此操作。
请在您自己的主机上确认这一点,不要只相信上面的说明。
docker network ls
docker network inspect onecli-sandboxes | grep -i internal您应看到 "Internal": true。如果输出为 false,则出站控制已关闭,网关又只是一项建议。请使用 docker network ls 输出的沙箱网络名称,因为 onecli-sandboxes 只是默认名称。
OneCLI sandbox 的隔离强度如何?
请仔细阅读本节。项目自身的描述非常强调“沙箱化”,但其实现机制只在一个地方有说明。
README 声明,每个 agent 都有“自己的隔离沙箱,其中包含文件系统和 shell”,并称 Sandbox Supervisor 是“在每个沙箱内运行的组件,通过供应商中立的 harness 接口通信,因此可以替换 agent runtime”。这两句话都没有说明隔离由什么实现。runner 的文档给出了答案:默认后端是 Docker(RUNNER_BACKEND=docker),沙箱是一个 Docker 容器,设置了内存上限、CPU 上限和进程数上限,并连接到内部网络。代码为其他后端预留了扩展点,文档则将 Kubernetes 和 microVM 等列为需要自行编写的模块。当前在你的机器上,沙箱就是一个容器。
文档没有说明的内容同样重要。文档没有威胁模型,也没有说明 Docker daemon 是否以 rootless 模式运行、是否启用用户命名空间重映射,或是否使用超出 Docker 默认配置的 seccomp 和 AppArmor 配置文件。文档也没有声称存在类似 gVisor 或 microVM 的内核边界。因此,应采用较窄的解读。各种上限是资源限制。内部网络确实限制了出站访问。agent 与主机之间的隔离能力,等同于标准 Docker 容器提供的能力;而容器与主机共享内核。
还需要考虑第二个事实。runner 服务挂载了 /var/run/docker.sock,因为它通过该路径创建沙箱。能够访问 Docker socket,等同于拥有主机上的 root 权限,因为能够调用该 API 的进程可以启动一个容器,并将主机文件系统挂载到容器内。所有基于 Docker 的 runner 都以这种方式工作。因此,runner 进程与 gateway 具有同等敏感性。
在上游明确记录边界之前,应将该边界视为未经证实。实际操作中,应遵循以下 3 点。
- 在只运行 OneCLI 的机器上部署。不要在该机器上运行无关的生产服务、共享数据库或其他团队的数据。
- 假设能够在沙箱内执行任意代码的 agent 可能访问主机,并通过将备份保存在其他机器上,使这种情况发生后仍可恢复。
- 在向同事说明 agent 已被隔离之前,先阅读
apps/runner/src,或向上游项目确认。
如需了解有文档记录的边界应如何描述,以及应向上游提出哪些问题,请对照真实 agent 沙箱边界的表现形式。两者的区别在于,是否有人明确记录了隔离机制及其无法阻止的行为。
许可证划分,以及构建前检查的原因
OneCLI 的核心部分采用 Apache-2.0 许可证,允许在生产环境中自行托管。名为 ee/ 的目录适用单独的 OneCLI Enterprise License:开发、测试和评估可免费使用,生产环境使用则需要订阅。2026 年 8 月 18 日发布的 v2.0.1 版本说明提到,项目恢复了可被 GitHub 检测到的 Apache-2.0 许可证文件,因此仓库页面上的许可证标识最近发生了变化。请检查实际部署的 tag,不要依据其他日期编写的摘要。
cd onecli && find . -type d -name ee -not -path '*/node_modules/*'这些路径下的内容都属于商业部分。如果计划依赖的功能位于其中,请先确认价格,再围绕该功能构建流程。
升级、迁移,以及绝不能丢失的那个文件
升级包括提升版本和重启。每次 up 时,都会先于 API 运行一次性迁移服务。如果迁移失败,整个堆栈会拒绝启动,而不是使用只完成一半迁移的架构提供服务。这正是需要的行为,因为升级失败会表现为服务中断,而不是悄无声息的数据损坏;docker compose logs migrations 说明了原因。
cd onecli/docker
docker compose pull
docker compose up -d --wait
docker compose logs migrations如果您使用安装脚本完成安装,请重新运行该脚本,不要手动拉取镜像。这样 compose 文件会始终与其中引用的镜像保持同步。
请备份两项内容。PostgreSQL 保存 agents、会话、memory 和加密密钥。docker/.env 文件保存 SECRET_ENCRYPTION_KEY。没有该密钥就无法读取加密密钥,因此仅备份数据库转储无法恢复任何可用数据。
cd onecli/docker
docker compose exec -T postgres pg_dump -U onecli onecli | gzip > ~/onecli-db.sql.gz
install -m 600 .env ~/onecli-env.backup请将两份备份都保存到服务器之外。所需流程与任何有状态 Compose 堆栈相同。如果您已经按计划备份和升级 Docker Compose 堆栈,只需将这两个路径加入流程即可,不必再手动处理。
无法正常工作时
- 堆栈始终无法变为健康状态,并且
docker compose up -d --wait以非零状态退出。先读取docker compose logs migrations,因为 API 会有意等待该服务。 - Agent 处于空闲状态,且没有出现 sandbox。确认
COMPOSE_PROFILES=runner位于docker/.env中,并确认docker compose ps列出了一个 runner。然后检查该 agent 是否已获得可用的模型密钥,因为没有模型密钥时不会启动 sandbox。 - 没有可用槽位。
RUNNER_MAX_SANDBOXES默认为 4,运行后台进程的 sandbox 会永久占用其槽位。docker ps可显示实际仍在运行的内容。 - 容器消失,或主机运行缓慢。主机内存不足。
dmesg -T | grep -i oom会记录内核因内存不足而终止进程的事件,单个 sandbox 就可能独占 2048 MB。 - Agent 的 HTTPS 请求失败,并显示证书验证错误,而不是身份验证错误。其 HTTP 客户端不信任网关的证书颁发机构。
- 删除 agent 后,旧容器或卷仍然存在。runner 每 60 秒执行一次协调,并销毁存在时间超过
RUNNER_ORPHAN_GRACE_SECONDS的孤立资源;RUNNER_ORPHAN_GRACE_SECONDS默认为 3600。因此,请等待 1 小时后再判断这是内存泄漏。
这适合您运行吗?
适用性测试很简单。当多人各自需要一个 agent,而您希望将凭据集中管理时,OneCLI 才有价值:只需维护一个存储、查看一份审计日志,并在一个控制面板中撤销某人的访问权限;撤销操作应立即生效。这是实际的运维问题。相比之下,将 API key 复制到 6 台笔记本电脑上是更糟糕的解决方案。
对于个人用户来说,这套组件过于复杂,而且没有实际收益。您需要运行 PostgreSQL、控制平面、网关和 runner,才能获得一个 agent;而网关要解决的凭据问题,在密钥唯一持有者就是您本人时,几乎不存在。更合适的做法是在配置更低的主机上运行单个 harness:在 VPS 上运行单个 agent harness 就能完成这项工作,而且只需其中一小部分内存。如果您还没有确定方向,自托管 AI agent 对比 中的概览是成本更低的起点。如果您不熟悉的是 agent 本身,而不是运行 agent 的服务器,请先学习 分阶段了解 agent 的实际工作方式,因为只有了解工具调用和内存存储的实际作用,您才能判断应向每个人的 agent 授予哪些权限。
FAQ
自托管 OneCLI 的最低服务器要求是什么?
需要 Docker Compose plugin 2.19 或更高版本,以及足够的内存。PostgreSQL 已包含在 compose 文件中,因此无需单独安装。内存决定可运行的规模:runner 默认会为每个 agent sandbox 分配 2048 MB;其文档要求除基础堆栈外额外提供约 10 GiB 可用内存,以支持默认上限的四个 sandbox;PostgreSQL 和四个长期运行的服务约占用 2 GB。4 GB 的服务器可同时运行一个 agent。16 GB 的服务器可稳定支持默认上限。1 GB 或 2 GB 的 VPS 根本无法启动 hosted agent。
OneCLI 需要 PostgreSQL,还是可以使用 SQLite?
需要 PostgreSQL。DATABASE_URL 的文档定义为 PostgreSQL 连接字符串;随附的 compose 文件会使用 postgres:18-alpine 和 pgdata 卷;独立的 migrations service 会在 API 启动前应用数据库 schema。文档未提供 SQLite 选项。如果您已经在其他位置运行 PostgreSQL,请将 DATABASE_URL 指向该实例,并保留 migrations service,因为迁移失败会停止整个堆栈,而不会让应用使用只应用了一部分的 schema 对外提供服务。
OneCLI agent sandbox 是真正的安全边界吗?
文档说明的机制是 Docker 容器,并设置内存、CPU 和进程数上限;容器连接到标记为 internal: true 的网络,因此除通过 gateway 外没有其他出站路由。出站控制确实生效,您可以使用 docker network inspect 进行验证。主机隔离能力属于容器级别,但上游没有发布威胁模型,也没有声明支持 rootless 或 user namespace,更没有说明使用 gVisor 或 microVM 等内核级边界。runner 还会挂载 /var/run/docker.sock,这相当于获得主机上的 root 权限。在上游明确说明这一边界之前,应视 agent 与主机之间的隔离为未经证实。请在专用服务器上运行 OneCLI,并将备份保存到该服务器之外。
是否需要为 OneCLI 开放任何入站端口?
不需要。runner 仅发起出站连接,不监听任何外部网络可访问的端口,因此在 NAT 后无需隧道即可运行。compose 文件默认将 dashboard、gateway、API 和 PostgreSQL 绑定到 127.0.0.1。您可以通过 SSH 隧道访问 dashboard;如果需要让多人访问,可在 10254 端口前配置带 TLS 的反向代理。10255 端口上的 gateway 供 agent 使用;在单服务器部署中,这些 agent 会通过内部 Docker 网络访问 gateway。
在公司内部使用 OneCLI 是否免费?
核心组件采用 Apache-2.0 许可,自托管生产环境使用无需商业许可。名为 ee/ 的目录受 OneCLI Enterprise License 约束;该许可可免费用于开发、测试和评估,但生产环境需要订阅。不同 release 之间的目录划分可能变化。2026 年 8 月 18 日发布的 v2.0.1 notes 提到恢复 GitHub 可识别的 Apache-2.0 许可文件,因此在围绕某个单独功能构建工作流之前,请检查您实际部署的 tag 中的 LICENSE 和 ee/ 目录。