第 9 章 · 01 五种部署形态


第 9 章 · 01 五种部署形态

本节摘要:最后一站停机坪。Agent Canvas 用五种形态交付:npm 全局包(npm i -g @openhands/agent-canvas 一条命令,bin/agent-canvas.mjs 用 uvx 拉起 Python 后端,零手工配置);Docker 三合一镜像(docker/Dockerfile 多阶段构建:前端静态产物 + agent-server 基础镜像 + pip 装的 automation,entrypoint.sh 479 行拉起三服务加 ingress 代理,单端口 8000 交付);Electron 桌面(electron-builder.config.mjs 产 macOS dmg/Windows nsis exe/Linux 包,afterPack 钩子把 600MB node_modules 削到约 10MB 产物);Helm chart(helm/agent-canvas/ StatefulSet/Service/Ingress,企业 K8s 部署);Vercel(vercel.json + vercelPreset,前端托管)。外围还有 README.windows.md 的 Windows Docker 指南、release-please 自动发版(.release-please-manifest.json"."​: "1.16.0")、20 个 CI workflows(npm-publish OIDC/docker/desktop 三平台/mock-LLM e2e)。最后给出个人/团队/企业的选型建议。

内容来源:原项目 bin/agent-canvas.mjsdocker/Dockerfile(171 行)与 docker/entrypoint.sh(479 行)、electron-builder.config.mjshelm/agent-canvas/vercel.jsonREADME.windows.md.github/workflows/ 目录,精读整理。

⚠️ 注意:五种形态共享同一份 config/defaults.json(版本钉扎、端口、路径)。Dockerfile 里专门有一个 config-gen 构建阶段把 JSON 转成 shell 可 source 的 defaults.env——避免容器运行时还要装 jq/python 解析 JSON。改端口/版本只改一处,五种形态同步生效,这是多形态交付不散架的关键。

学习目标

阅读完本节,你应当能够:

  1. 说清 npm 全局包的启动链路:bin 脚本 → uvx → agent-server/automation → static-server。
  2. 逐段读懂 Docker 三合一:Dockerfile 三阶段、entrypoint 的服务启动序列、ingress 路由表、安全默认(密钥自动生成)。
  3. 解释 Electron 打包的"600MB 到 10MB"瘦身术与三平台产物。
  4. 了解 Helm chart 的资源清单与 Vercel 的前端托管方式。
  5. 概述 release-please 发版与 20 个 workflows 的分工,并按用户类型给出部署选型。

一、npm 全局包:一条命令的完整栈

最轻的交付形态:npm i -g @openhands/agent-canvas,然后运行 agent-canvaspackage.jsonbin 字段把命令指到 bin/agent-canvas.mjs,其头部自述:

#!/usr/bin/env node /** * CLI entry point for @openhands/agent-canvas * * Runs the full Agent Canvas stack locally by default: * - Agent-server via uvx * - Automation backend via uvx * - Pre-built static frontend * * This is the production equivalent of `npm run dev` - it runs the full stack * but serves pre-built static assets instead of the Vite dev server. */

链路要点:Python 后端用 uvx 按需拉起——用户机器不需要预装 Python 环境、不需要 pip install,uvx 按 defaults.json 钉扎的版本(agent-server 1.44.1/automation 1.10.0)创建隔离环境执行;前端是 npm 包里预构建的静态产物,由 static-server 服务。CLI 提供 -v/--version--info--public 等旗标,--info 的输出直接打印钉扎版本、最低兼容版本(1.28.0)与默认端口——把第 7 章讲过的版本对表能力交到用户手里。

这种形态的工程妙处:一份 JS 交付物内嵌了一个由 uvx 编排的 Python 微服务栈,用户感知不到语言边界。首个第八章提过的细节在此兑现——mock-LLM e2e 启动正是这个二进制,保证"测的就是用户拿到的"。

二、Docker 三合一:单镜像、单端口、单命令

2.1 Dockerfile:三阶段构建

docker/Dockerfile 的头部注释即架构图:

# Combines three services into a single image: # 1. Agent Server — from ghcr.io/openhands/agent-server (upstream SDK image) # 2. Automation — installed via pip from openhands-automation # 3. Frontend — agent-canvas static build served by Node.js # # The entrypoint starts all three services and an ingress proxy that unifies # them behind a single port (default 8000): # /api/automation/* → automation backend (:18001) # /api/*, /sockets → agent server (:18000) # /* (default) → static frontend + SPA fallback

三个构建阶段:

阶段 基础镜像 产物
frontend-build node:24-slim npm ci && npm run build,产出静态前端(构建参数烘焙 VITE_BASE_PATH=/canvas 与 PostHog key)
config-gen node:24-slim 用一段内联 node 脚本把 config/defaults.json 转成 defaults.env(端口/路径/遥测键的 shell 变量)
final ${AGENT_SERVER_IMAGE}(上游 SDK 镜像) pip 装 automation,拷前端产物、static-server 与五个运行时依赖(sirv/httpxy 等)、tools/、defaults.env、entrypoint

final 阶段直接以 agent-server 官方镜像为底,共享其 Python 依赖(openhands-sdk/fastapi/uvicorn),只补 automation 特有的包(asyncpg 等)——不重复造 Python 环境。收尾的安全细节:VOLUME ["/home/openhands/.openhands", "/projects"] 声明两个持久卷(设置/会话/自动化数据库,与用户代码),并预建目录、chown 给非 root 的 openhands 用户,避免匿名卷 root 属主导致运行时写不进。

2.2 entrypoint.sh:479 行的编排与安全默认

入口脚本 docker/entrypoint.sh 依次做六件事,挑关键段落看:

第一,加载 defaults.env 并解析端口/路径,对 VSCODE_BASE_PATH 做严格的单段校验(拒绝 /api 等保留前缀——编辑器路由若撞上 API 前缀会静默劫持整站)。

第二,密钥自动生成与持久化——这是"开箱即用"与"不裸奔"的平衡术:

# OH_SECRET_KEY is required for settings/secrets encryption. Without it the # agent-server refuses to return encrypted secrets → conversation creation # fails with a 503. Auto-generate and persist (just like the session API key) # so the image never runs with a known default. SECRET_KEY_FILE="${STATE_DIR}/secret-key.txt" if [ -z "${OH_SECRET_KEY:-}" ]; then if [ -f "$SECRET_KEY_FILE" ]; then OH_SECRET_KEY="$(cat "$SECRET_KEY_FILE")" else OH_SECRET_KEY="$(head -c 32 /dev/urandom | od -An -tx1 | tr -d ' \n')" mkdir -p "$(dirname "$SECRET_KEY_FILE")" printf '%s' "$OH_SECRET_KEY" > "$SECRET_KEY_FILE" chmod 600 "$SECRET_KEY_FILE" log "Generated OH_SECRET_KEY (persisted to $SECRET_KEY_FILE)" fi fi export OH_SECRET_KEY

不给密钥就从 /dev/urandom 生成 32 字节随机数、落盘、chmod 600,重启复用;会话 API key 同样处理,并让 agent-server 与 automation 共享同一凭据同一 X-Session-API-Key——一张钥匙开全栈。镜像永远不会以"已知默认密钥"运行。

第三,拉起 agent-server 与 automation(后者默认 SQLite,免外部 Postgres,AUTOMATION_DB_URL 可覆盖),wait_for_port 双线程并行等待就绪。

第四,启动 ingress 静态服务器——单端口的核心:

node /opt/agent-canvas/static-server.mjs \ --port "$PORT" \ --host :: \ --dir /opt/agent-canvas/frontend \ --base-path "$AGENT_CANVAS_BASE_PATH" \ --session-api-key "$EFFECTIVE_SESSION_KEY" \ --runtime-services-info "$RUNTIME_SERVICES_INFO" \ --route "/api/automation=http://127.0.0.1:${AUTOMATION_PORT}" \ --route "/api=http://127.0.0.1:${AGENT_SERVER_PORT}" \ --route "/server_info=http://127.0.0.1:${AGENT_SERVER_PORT}" \ --route "/sockets=http://127.0.0.1:${AGENT_SERVER_PORT}" \ --route "/alive=http://127.0.0.1:${AGENT_SERVER_PORT}" \ --route "/health=http://127.0.0.1:${AGENT_SERVER_PORT}" \ --route "/ready=http://127.0.0.1:${AGENT_SERVER_PORT}" \ --route "/docs=http://127.0.0.1:${AGENT_SERVER_PORT}" \ --route "/redoc=http://127.0.0.1:${AGENT_SERVER_PORT}" \ --route "/openapi.json=http://127.0.0.1:${AGENT_SERVER_PORT}" \ --route "$VSCODE_ROUTE" \ --vscode-base-path "$VSCODE_BASE_PATH" \ --no-referrer-prefix "$VSCODE_BASE_PATH" &

这张路由表就是第 1 章总控台讲的 ingress 单端口架构在 Docker 里的落地:/api/automation 去 automation(注意先于 /api 注册,长前缀优先),其余 /api/sockets/server_info、健康检查、OpenAPI 文档全转 agent-server,剩下的走静态前端 + SPA fallback,/vscode 前缀转内置编辑器。--host :: 双栈绑定让 localhost 无论解析到 IPv4 还是 IPv6 都通。会话 key 由静态服务器在 serve 时注入 HTML,前端零配置完成认证。

第五,可选的公共模式第二实例(PUBLIC_MODE_PORT 时不注入 key、要求认证,供认证模式 e2e 用)。第六,守护循环:

while kill -0 "$STATIC_PID" 2>/dev/null; do sleep 10 & wait $! done

容器随 ingress 存活;后端崩了不连坐(代理对死路由回 502,模拟非 Docker 部署下各进程独立的行为)。sleep & wait $! 的写法让信号能立即中断 bash 内建的 wait,收尾 trap 即时清理。第 8 章 AGENTS.md 摘录里对这段的详注("Entrypoint crash resilience")说明它连 e2e 的稳定性都考虑到了。

💡 驾驶舱要点:Docker 形态的本质是把第 1 章的"多后端架构图"压缩进一个容器:三个真实服务 + 一个代理,对外只有一个 8000。用户 docker run -p 8000:8000 加两个卷挂载,就得到与开发栈拓扑完全一致的生产栈——这同样是 mock-LLM-docker e2e 能直接复用 npm 路径 spec 的原因。

三、Electron 桌面:三平台安装包

electron/ 目录(main.mjs 主进程、preload.cjs、loading.html 启动屏、window-url-policy)加根目录的 electron-builder.config.mjs 构成桌面形态。配置文件头部一段长注释讲述了打包战争中最精彩的一役——600MB 到 10MB:

// The fix is the `afterPack` hook below: after electron-builder has // finished copying files, we rm -rf the bundled Resources/app/node_modules/ // directory, then copy back the dependency closure of RUNTIME_PACKAGES // (~200 KB). The build wastes a few seconds copying files we immediately // delete, but the final artifact is correctly tiny (~10 MB vs ~600 MB).

问题:electron-builder 的依赖搜索会从根目录 npm list 拿到整个 hoisted 树(约 342 个包/600MB:Vite、React、Monaco……),全塞进安装包;而桌面运行时(main.mjs 与两个子进程服务器)只用到 Node 内置模块加 sirv/httpxy 两个包。解法:afterPack 钩子先删光再按需拷回约 200KB 的闭包。仓库里 checkout 内测试会"碰巧"从仓库 node_modules 解析成功、装到 /Applications 才崩——注释原话记录了这个坑,防止后人"优化"回去。

三平台产物由三个 workflow 分头构建:desktop-macos.yml(校验 dist-electron 下恰好一个 dmg)、desktop-windows.yml(nsis x64 exe)、desktop-linux.yml(deb);Windows 走 SignPath 代码签名,appId: "dev.openhands.agent-canvas"。桌面包同样内嵌 uv 与 Node 发行版(extraResources),离线拉起同款后端栈。

四、Helm 与 Vercel:两端延伸

Helm chart(helm/agent-canvas/):企业 K8s 形态。模板清单很克制——statefulset.yaml(有状态,配合持久卷)、service.yamlingress.yamlserviceaccount.yamlrbac.yaml,加 Chart.yaml/values.yaml/README。它部署的就是上面那个 Docker 三合一镜像:K8s 里一个 StatefulSet 跑全栈容器,Ingress 对接集群网关。chart 版本由 bump-chart.yml workflow 随镜像版本自动 bump——镜像发新版,chart 跟着升。

Vercel(vercel.json + react-router.config.ts):纯前端托管形态。react-router(v7 模式的 Vite 插件)自带 vercelPreset,构建输出直接适配 Vercel 的静态/函数布局;vercel.json 只剩一行自定义安装命令 "installCommand": "bash scripts/vercel-install.sh"。配置注释里那句"react-router.config.ts unpacks build/client/ into build/ for non-Vercel builds"点明关系:Vercel 用平台原生目录,其他部署(npm/Docker/Electron)把同一产物解包到通用布局。前端与后端分离托管的场景(比如后端跑在自己机房)用它。

五、发版与 CI:release-please 和 20 个 workflows

自动发版走 release-please:按 Conventional Commits 聚合变更日志、递增版本、打 tag。.release-please-manifest.json 钉住仓库根的当前版本({".": "1.16.0"}),与 package.jsonconfig/defaults.jsonversions.agentCanvas 三处联动。发版后各下游 workflow 被 tag 事件唤醒。

.github/workflows/ 下 20 个文件,按职能分组:

workflows 职责
主 CI ci.ymlpr.ymlci-script-tests.yml 单测 + lint + 类型检查;PR 描述人审检查
e2e mock-llm-e2e.ymlmock-llm-e2e-comment.ymlmock-llm-docker-e2e.yml 第 8 章的全链路回归(npm 与 Docker 双路径)+ PR 报告
发布 npm-publish.yml(OIDC 免密发 npm)、docker.yml(构建推 GHCR)、desktop-*.yml(三平台安装包)、release.ymlrelease-ready.ymlbump-chart.yml 五形态交付物
协同 sdk-version-sync.ymlpr-artifacts.yml 跨仓库版本对表;live-e2e 媒体产物清理
社区 issue-duplicate-checker.ymlissue-readiness-check.ymlremove-duplicate-candidate-label.yml issue 分诊自动化

npm-publish.yml 用 OIDC 而非长期 npm token——发布凭据由 GitHub 短期签发,泄漏面最小化。TESTING_MATRIX(第 8 章)管的就是这些自动发布之后、人工冒烟之前的最后一公里。

六、部署选型建议

用户 推荐 理由
个人开发者 npm 全局包 一条命令,uvx 自动管 Python,升级即 npm update -g
小团队/服务器 Docker 三合一 环境完全一致,卷挂载持久化,单端口好反代,README.windows.md 连 Windows Docker Desktop 的 PowerShell 命令都写好了
桌面偏好者 Electron dmg/exe/deb 安装包,双击即用,含启动屏与更新检查
企业平台 Helm K8s 声明式部署,Ingress/RBAC 齐备,随镜像自动升 chart
仅前端/自管后端 Vercel 前端托管零运维,后端自选位置

选型的潜台词:五种形态不是五个产品,是同一包产物的五种包装。npm 包里的 build/、Docker 镜像里的 /opt/agent-canvas/frontend、Electron 的 Resources/app/build、Vercel 的部署产物,都是同一次 npm run build:app 的输出;后端栈都由同一份 defaults.json 编排。这就是为什么第 8 章的测试矩阵只盯 npm 与 Docker 两条主干——其余形态共享同源产物,风险集中在生产处。

本节要点回顾

  1. npm 全局包:bin/agent-canvas.mjs 用 uvx 按钉扎版本拉起 agent-server 与 automation,服务预构建静态前端;--info 打印版本对表信息;一份 JS 交付物内嵌 Python 微服务栈。
  2. Docker 三合一:Dockerfile 三阶段(前端构建/config-gen 转 defaults.env/以 agent-server 镜像为底装 automation);entrypoint 六步——路径校验、密钥自动生成持久化(urandom 32 字节,全栈共享 X-Session-API-Key)、双服务就绪等待、ingress 路由表(单端口 8000,/api/automation 长前缀优先)、可选公共模式、sleep & wait 守护;VOLUME 持久化 .openhands 与 /projects。
  3. Electron:afterPack 删 600MB node_modules 再拷回约 200KB 闭包,产物约 10MB;三平台 workflow 产 dmg/nsis exe/deb,Windows 走 SignPath 签名,内嵌 uv 与 Node 发行版。
  4. Helm/Vercel:StatefulSet+Service+Ingress+RBAC 的克制 chart,部署同一镜像,版本自动 bump;Vercel 用 vercelPreset + 一行 installCommand 定制。
  5. 发版:release-please 按 Conventional Commits 自动发版,manifest 钉 1.16.0;20 个 workflows 覆盖主 CI/e2e 双路径/五形态发布/跨仓库同步/issue 分诊;npm 发布用 OIDC 免长期 token。
  6. 选型:个人 npm、团队 Docker、桌面 Electron、企业 Helm、纯前端 Vercel;五形态同源产物,测试守主干即可。

下一节:第 9 章 · 02 全书回顾——九站串讲与核心哲学,全书最后一节。


作者与出处
原作者: 灏天文库
整理: 灏天文库整理
本站整理收录,版权归原作者/开源协议所有;欢迎通过原文链接访问源仓库。
发布者: 作者: 灏天文库 转发
评论区 (0)
U