Claude Code 自托管环境怎么搭:quickstart 到 deploy 的顺序不能颠倒
多数人搭这套东西的处境是这样的:代码不能出内网,但又想用上 Claude Code 的 cloud session;于是找到「自托管环境」(self-hosted environments)这一页,照着 quickstart 敲完命令,看见状态变绿了,顺手就把真实仓库接上去。
官方文档在 quickstart 页开头就把这条路堵住了:在你连接真实仓库或内部系统之前,先走完 Deploy to production 那一页,它覆盖安全姿态、出网控制、git 凭证与编排。换句话说 quickstart 给的是「最小的能跑起来的那一个」——一台主机、一个 runner、一个测试会话,它从来不是生产配置。这篇按文档自己给的顺序过一遍。
按文档的说法,一个自托管环境让 Claude Code 的 cloud session 跑在你们组织自己运维的基础设施上,由你部署的 runner 进程执行。你会在两个面之间来回:claude.ai 那边负责创建环境、看状态、把会话路由过去,runner 主机上的终端负责其余的一切。
这套能力目前标注为 public beta,且限于 Team 与 Enterprise 计划。 三页文档开头都挂着同一个 Note,别当成已经稳定的能力来规划。
一、前置条件:这一段最容易被跳过
组织与角色。 claude.ai 侧需要由 Owner 或 admin 在 Cloud environments 管理页打开 Allow self-hosted environments——文档写明,没打开之前 New 按钮根本不出现。另外要给组织配好 GitHub connection,开发者起会话时才能选仓库。手里没这个角色也不卡:文档写明后续的 runner 与终端步骤不需要任何 claude.ai 角色,有角色的人把环境建好、把密钥交给你即可。
主机与网络。 有一条对本站读者最要紧:Windows 不被支持作为 runner 主机,文档给的做法是把 runner 跑在 Linux 容器里。 开发者自己的工作站不受影响,因为会话是从浏览器里的 claude.ai 发起的。主机需要 Linux 或 macOS 主机/容器,出站 HTTPS 能到 api.anthropic.com、能到 claude.ai 及其在安装步骤中重定向到的下载主机、能到你的 git 主机。
还有一条纯运维、排查起来却最费时间的:主机时钟要与真实时间同步(文档举了 NTP),文档写明偏差超过五分钟会导致认证失败。
主机上的软件。 需要 Claude Code v2.1.224 或更高——runner 是标准 claude 二进制的一部分,更早版本不认识 self-hosted-runner 子命令;以及 Git 2.24 或更新。文档给了确认命令:
claude self-hosted-runner --help
就绪的主机会打印 runner 的用法文本,其中列出 --environment-secret-file 这类参数;低于 2.1.224 的版本会打印通用的 claude --help 输出。这是个很划算的判据,先跑它再往下走。
二、按文档的顺序走一遍
可选的引导式 setup
claude self-hosted-runner setup 会带你创建环境、用保存的密钥文件起本地 runner、确认注册,并把一份速查表写到 ./runner-setup/CHEAT-SHEET.md。条件写得很死:要在一台已用 claude auth login 登录、账号持有 Owner 或 admin 角色的机器上跑,用 API key 或第三方模型供应商时不可用。文档还提醒,低于 2.1.224 的版本跑这条命令不会进引导,而是把这几个词当成 prompt 起了一个普通会话——所以上面那个版本判据要先过。
第 1 步:创建环境,拿到只显示一次的密钥
在 Cloud environments 管理页的 Self-hosted environments 下选 New,命名,Create;向导第二步选 Copy environment key 复制环境密钥。文档写明这个值只展示一次、事后取不回来,并且有创建后的有效期(文档当前写的是 365 天,以官方文档最新内容为准)。环境的 ccpool_... ID 会一直留在详情里,后面做 token 校验的 aud 检查、从 CI 派发测试会话都要用它。
轮换的顺序文档也写死了:先从环境的 Configuration 页签创建新密钥,铺到 runner,再吊销旧的。持有已吊销密钥的 runner 会在下一次带认证的轮询失败并退出,日志里是 poll auth failed。
第 2 步:把密钥落到文件,起 runner
mkdir -p /etc/claude
(umask 077 && cat > /etc/claude/environment-secret)
第二条从终端读入:粘贴、回车、Ctrl-D;子 shell 的 umask 让文件只有属主可读,同时避免密钥进 shell 历史。文档写明 /etc/claude 这个路径需要 root,换成任何 runner 进程能读的路径都行,但要把两条命令和 --environment-secret-file 的值一起改。
claude self-hosted-runner --environment-secret-file '/etc/claude/environment-secret' --base-dir '<writable-dir>'
--base-dir 填一个 runner 能写或能创建的绝对路径,runner 启动时创建它,然后把仓库检出、在其下建每会话目录。不传时默认 /workspace,而这个默认只在该目录已存在且可写、或你以 root 起 runner 时才成立。写不进去的话,runner 不是注册失败,而是在启动阶段就带着一条点名目录的错误退出。
第 3、4 步:确认注册,再路由一个会话
回到 Cloud environments 页,环境状态会从 No runners deployed 变成 Healthy,Activity 里能看到 runner 本身。然后在 claude.ai/code 起会话,从环境选择器里挑你的环境。这里文档有个务实提醒:runner 用主机上已有的 git 凭证克隆,所以测试时挑一个这台主机本来就能克隆的仓库,或者干脆挑公开仓库;私有仓库在生产里怎么给凭证是 deploy 页的事。被接走时 runner 输出里会打 Picked up session <session-id>,带上活跃数与容量。
第 5 步:从终端追发消息
会话跑起来后,可以从任意一台已 claude auth login 的机器发追加消息,不必是起会话那台:
claude -p "your message" --cloud <session-id>
<session-id> 可传裸的 session_... 或 cse_... ID,也可直接传会话的 claude.ai/code URL;成功会打印 Sent to cloud session.。
三、接真实系统之前的必配清单
quickstart 到这里就结束了,而文档明确说这时候还不能接生产。deploy 页把要点放在「Harden your deployment」一节,理由文档自己写了:runner 会代表你组织里的任何成员,在你的基础设施上执行由模型指挥的任意代码。
| 配置点 | 文档写明的做法 |
|---|---|
| 容器生命周期 | 每个 runner 进程跑在一次性容器/VM 里,进程退出即销毁,配 --capacity 1 与默认的 --drain-grace-sec 0,一个容器只服务一个会话 |
| 镜像里的凭证 | 不放长期 SSH key、云厂商凭证或超出单次会话所需的 PAT;会话内用到的凭证由 wrapper script 按会话铸造 |
| 首次 clone 的凭证 | clone 发生在 wrapper 之前,因此要用 checkout lifecycle hook 或 --use-anthropic-git-proxy |
| 环境密钥的位置 | 固定机队里密钥落在每台 runner 主机上,会话代码读得到;文档建议改用 on-demand runners,让密钥只留在从不跑用户代码的编排器主机上 |
| 出网 | 默认拒绝的 egress,只放行网络需求表里的主机与会话确实要访问的内部服务 |
| 主机 IAM | 最小权限;会话应通过 wrapper 拿自己的凭证,而不是继承主机身份 |
| 云元数据端点 | 子网级策略拦不住 link-local 流量,要在容器内挡:IMDSv2 hop limit 为一、GKE Workload Identity metadata concealment,或对 169.254.169.254 显式 deny |
| 文件系统隔离 | 每个 runner 进程有自己的工作目录;--hooks-dir、wrapper 脚本与主机的 ~/.claude/ 对会话只读 |
| 派发范围 | 组织内任何成员都能把会话派到任意环境,dispatch 层面没有按环境的访问控制;--lock-to-account 限的是某台主机执行哪个账号的会话,限不住派发本身 |
| 仓库设置守卫 | --confine-repo-settings 三档:默认 warn 记录违规但仍拉起会话,enforce 拒绝会话,off 关掉扫描 |
最后一行值得单独说,它是少数会直接改变「会不会跑起来」的开关。守卫扫描仓库已提交设置里的三类东西:解析到会话工作区之外的授权(additionalDirectories 条目,permissions.allow 里的 Edit、Write、NotebookEdit 规则,或 sandbox.filesystem.allowWrite/allowRead 条目);非空的 env 块;以及 sandbox.enabled: false 这类推翻运维姿态的覆盖。文档同时写明它不覆盖仓库 hooks、.mcp.json 和 Bash 规则——别把它当完整的准入检查。
网络侧只有两个主机始终需要:api.anthropic.com(443/HTTPS,SCM 连接器另走 WSS),以及你的 git 主机(443 或 22;用 --use-anthropic-git-proxy 时不需要,因为 git 流量改走 api.anthropic.com)。其余若干主机按配置条件性需要,deploy 页有完整表格。还有一条容易踩:组织的 IP allowlist 默认不覆盖自托管 runner 的流量,文档明说别把它当作 runner 或会话流量的网络控制。
git 这块两条路选一条:用 --configure-git(或 SELF_HOSTED_RUNNER_CONFIGURE_GIT=1)让 runner 启动时写全局 git 配置,或自己在镜像里配。自己配的话文档给的是系统级写法,不管 runner 以哪个用户跑都生效:
RUN git config --system user.name "Claude" && \
git config --system user.email "noreply@anthropic.com"
没有身份时 git commit 会以 Please tell me who you are 失败。检出目录的 uid 与 runner 进程不同的话还要补 safe.directory。另外 runner 把交互提示全关了——GIT_TERMINAL_PROMPT=0、SSH 的 BatchMode=yes、GCM_INTERACTIVE=never,并清掉 core.askPass(要用 askpass 就改走 GIT_ASKPASS 环境变量)——所以你配的凭证机制必须能零交互工作。git 版本地板按功能分档:--configure-git 的 SSH 提交签名要 2.34+,--use-anthropic-git-proxy 要 2.32+,从 --push-outcome-on-release 推的分支恢复会话要 2.29+;三个都不用、自己管 git 身份的话 2.24 就够。
四、边界:文档自己说了不保证的部分
- 整套能力是 public beta,且只在 Team 与 Enterprise 计划上;Windows 不支持作为 runner 主机。
- deploy 页给的 Kubernetes 与 Compose 配方用的是高于 1 的
--capacity,文档在同一页明说那不提供硬化一节要求的每会话容器隔离;接生产前要么降到--capacity 1、一容器一会话,要么改用 on-demand runners。Compose 那份用 Docker 重启策略,会带着可写层重启同一容器(即复用文件系统),文档写明它适合评估用途。 - 恢复的会话会丢未推送的工作:会话被释放后再收到消息,会在一台全新的 runner 上从起始分支重新克隆。
--push-outcome-on-release能尽力先推一把结果分支,但保住的是已提交的内容而非脏工作区;启用前文档要求先限制谁能推claude/*引用。 - 私有仓库不能中途加进会话:会话开始后再加的仓库不会带凭证克隆,所以创建会话时要把要用的仓库全选上。
- 连接器流量不从你的网络出去:Anthropic 从自己的基础设施调用连接器工具;要让工具流量留在内网,得把等价工具做成 runner 镜像上的本地 MCP server。
/healthz只表示进程活着,检测不到「活着但停止轮询」;文档给的做法是对/metrics里的last_poll_age_seconds告警。- 至于一个环境能承载多大规模、延迟如何,官方文档没有说明这一点,本文也不做推算。
五、怎么确认真的配对了
- 版本就绪:
claude self-hosted-runner --help打印的是 runner 用法(含--environment-secret-file),不是通用帮助。 - runner 注册:管理页状态从 No runners deployed 变 Healthy。文档写明若你没有 claude.ai 角色,runner 自己的日志行给的是同样的信号。
- 会话被接走:runner 输出里出现
Picked up session <session-id>。会话一直排队不动的话,troubleshooting 里第一条要查的是——每个在线 runner 可能都被锁到了别的账号上。 - 启动期失败要看 stderr:
cannot create or write to base directory与 base 目录检查超时这两条,是在 runner 打开--log-file之前打到 stderr 的,要去终端或容器日志里找,不是去日志文件里找。 - 排空时间够不够:runner 在启动时把完整排空路径需要的总时长打进日志,
terminationGracePeriodSeconds(Kubernetes)或stop_grace_period(Compose)至少要给到那个数。文档明说 Kubernetes 的默认值短于 runner 的排空路径,会在排空完成前把 pod 停掉。 - 兜底诊断:
claude self-hosted-runner doctor。文档写明它以只读方式访问 runner 的日志与状态,唯一能做的改动是把卡住的会话重新排队;跑之前先在该主机claude auth login,否则(例如主机用 API key 认证时)它只能看本地健康端点、指标,以及在你用了--log-file时才读得到的 runner 日志。
以上命令均按官方文档原文抄录;组合多个参数时为按官方文档中的参数语义组合的示例,未经实测,以官方文档与 --help 的实际输出为准。
回到标题那句。三页文档的分工是清楚的:quickstart 让你确认链路是通的,deploy 页决定这套东西能不能碰你的生产系统,configuration 页解决默认行为不够用时往哪儿伸手。把第二页跳过去直接接内网仓库,等于让组织里每一个成员都拿到了在这台主机上执行代码的入口——这不是推测,是 deploy 页原话写明的 dispatch 语义。
本文依据 Claude Code 官方文档(code.claude.com/docs)于 2026-08-17 的公开内容整理。
该产品闭源,本文只复述官方文档写明的机制,不推断其内部实现;
我们没有对文中涉及的功能做过实测,因此不涉及界面外观、操作手感与运行速度的任何描述。
该产品迭代频繁,文中涉及的命令、配置项与默认值随版本变动,请以官方文档最新内容为准。
本文不涉及价格、额度与限流的具体数值,相关信息请以官方定价与用量说明页为准。
安全与合规相关做法请结合自身环境评估,本文不构成安全方案建议。