TL;DR
每个 CNB Workspace 都有一个 business_id,它是外部访问的子域名。
通过 API GET /workspace/list 就能拿到,不需要 SSH。
但 API 偶尔返回 errcode=5,端口也因项目而异——这两个坑,值得单独记一笔。
背景
我在 CNB 平台上跑了好几个 Workspace:Gallery(前端展示)、DesktopOwn(桌面环境)、opencode(AI Agent)。它们各自暴露不同的服务端口——Ollama 在 11434,noVNC 桌面在 10001,React 开发服务器在 5173。
问题只有一个:我怎么在外网访问这些服务?
CNB 为每个 Workspace 提供了一个 Proxy URL,格式类似 https://5mxv4etp88-10001.cnb.run。问题是,这个 URL 无法直接预测——你要么 SSH 进去看环境变量,要么另寻他路。
而我遇到的情况是:DesktopOwn 这个 Workspace,压根不支持 SSH(它的自定义镜像没有开 SSH 服务)。
所以只能另想办法。
发现:business_id == Proxy Hash
事情是从一次偶然的对比开始的。
我用 cnb workspace list-workspaces --status running 查看正在跑的 Workspace,发现输出里有一列 business_id。而另一边,能 SSH 的 Workspace 里 env | grep CNB_VSCODE_PROXY_URI 出来的 hash 值,跟 business_id 一模一样。
那一刻的直觉:这两个值就是同一个东西。
于是我用一个 Workspace 做了验证:
SSH 内: CNB_VSCODE_PROXY_URI = https://ky6otflwmk-{{port}}.cnb.run
API 里: business_id = ky6otflwmk
完全吻合。
这就意味着:不需要 SSH,只需要调用一个 API 就能拿到 Proxy URL 的全部拼图。
流程简化为:
API 获取 business_id → 知道项目端口 → 构造完整 URL
踩坑 1:API 返回 errcode=5
验证通过后,我打算把这条路固化到自动化脚本里。结果第二天就翻车了。
我新启动了一个 Workspace,想用 API 拿新的 business_id。结果:
{
"errcode": 5,
"errmsg": "success",
"list": []
}
list 是空的,business_id 根本拿不到。
可是 Workspace 明明在跑。CLI 命令行也能看到。为什么同样的 API 路径返回空?
尝试了几次之后发现:这似乎是 API 的一个间歇性问题——偶尔返回空列表,重试一次就恢复正常。
教训:API 返回空列表时不要慌。重试一次(或者隔几秒再试),通常就能拿到数据。
踩坑 2:端口不是固定的
解决了 business_id 的问题后,我以为万事大吉了。然后遇到了第二个坑。
Proxy URL 的完整格式是:
https://{business_id}-{port}.cnb.run
port 是多少?直觉告诉我,CNB 的端口应该都是 10001。毕竟之前见过的 Workspace 端口全是这个。
但当我尝试用 10001 去访问 opencode Workspace 时——连不上。
查了一下才知道:CNB_VSCODE_PROXY_URI 环境变量本身是模板:
https://{business_id}-{{port}}.cnb.run
其中的 {{port}} 是模板占位符,具体替换成什么值,完全由每个项目在 .cnb.yml 中通过 sed 决定。
也就是说:
- DesktopOwn 的
.cnb.yml写了sed "s/{{port}}/10001/"→ 端口 10001 - opencode 的
.cnb.yml写了sed "s/{{port}}/5173/"→ 端口 5173 - ollama 跑在 11434
端口不是平台统一分配的,是项目自己决定的。
踩坑 3:CLI --branch 参数其实是坏的
这个坑虽然跟 Proxy URL 不完全直接相关,但它影响了整个自动化流程。
我本来想用 CLI 启动 Workspace 然后拿 proxy URL:
cnb workspace start-workspace --branch main
结果请求体永远是 {"branch":"master"}。无论你怎么传 --branch,CLI 发出去的都是 master。
所以如果想在非默认分支上启动 Workspace,必须绕过 CLI,直接用:
curl -X POST "https://api.cnb.cool/workspace/start" \
-H "Content-Type: application/json" \
-d '{"slug":"my-project","branch":"main"}'
最终工作流
综合所有踩坑,最终的可靠流程是:
- 调用 API
GET /workspace/list→ 拿到 running workspace 的business_id - 查端口表(或读
.cnb.yml)→ 确认项目用哪个端口 - 构造 URL
https://{business_id}-{port}.cnb.run - 验证:curl 过去看看有没有响应
如果 API 返回 errcode=5 → 重试一次。
如果 Workspace 重启了 → business_id 不变,URL 不变(在同一 Workspace 的生命周期内是稳定的)。
如果需要启动新 Workspace → 用原始 API 而不是 CLI(避免 --branch bug)。
验证
# 获取 business_id
curl -s "https://api.cnb.cool/workspace/list" \
-H "accept: application/json" \
-H "Authorization: Bearer $CNB_TOKEN" \
| python3 -c "
import sys, json
d = json.load(sys.stdin)
for w in d.get('list', []):
print(f'{w[\"sn\"]:25s} business_id={w.get(\"business_id\",\"-\")}')" \
| grep running
# 构造并验证
BUSINESS_ID="5mxv4etp88" # 替换为上面拿到的值
PORT=10001 # 替换为项目对应的端口
curl -sI "https://${BUSINESS_ID}-${PORT}.cnb.run" | head -5
相关
- Skill 文档:
~/.agents/skills/cnb-workspace-proxy-url/SKILL.md - ADR:
knowledge/notes/adr/0001-ollama-proxy-architecture.md

