我家里有一台常用的小 Mac mini。它平时就在那儿供电、联网,装着我做项目需要的工具;与其每次打包都等一台临时环境重新准备,不如让它替 GitHub Actions 接构建任务。于是我把它接成了自托管 Runner。
这件事听起来像“换一个 runs-on”,但我真正要搞明白的是:GitHub 负责什么时候派活,Runner 负责在哪里、以谁的身份执行活。 把工作交给自己的机器,省下的是重建环境的麻烦;接过来的则是这台机器的维护和安全责任。
这笔账什么时候划算#
我把它归到“省钱”笔记,但不是说接上自托管 Runner 就能立刻少付钱。按 GitHub 当前的 Actions 计费说明,自托管 Runner 的 Actions 使用本身不计费;私有仓库用 GitHub 托管 Runner 则有账户自带的免费分钟,超出后才可能产生分钟费用。我已经有一台常开的 Mac mini,重复构建和打包可以用这台机器接活,这是我选择它的前提。
是否实际省钱要看当月托管 Runner 是否已经超出免费额度,再把 Mac 的电费、网络、维护时间和机器折旧算进去。如果使用量很少,托管 Runner 可能更省心;如果本来就有稳定在线的 Mac,而且打包频繁,自托管更值得比较。我没有给这台机器做过完整账单对照,所以这里分享的是省钱思路和核算方法,不报一个未经验证的节省金额。
Runner 到底是什么#
把 Actions 想成一张任务单比较容易懂。仓库里的 workflow 写着“合并到主线时测试、构建、打包”;GitHub 收到事件后把 job 放进队列;Runner 领走 job,在本机 checkout 代码、执行命令、上传结果。Runner 可以是 GitHub 提供的临时机器,也可以是我这台常开的 Mac mini。Runner 本身不决定什么代码可以发布,工作流的触发条件、仓库权限和部署门禁才决定它能做什么。
我会怎样从零接上一台 Mac#
第一步,在私有仓库打开 Settings → Actions → Runners → New self-hosted runner,选择 macOS 和机器的架构,按 GitHub 当页生成的命令下载、配置并启动 Runner。那张页面会给一个短时有效的注册令牌,不要把它写进脚本仓库或截图公开。成功时仓库的 Runners 页面会显示机器在线;本机进程会开始等待 job。GitHub 官方安装步骤是这里的准绳。
第二步,给它一个用途明确的自定义标签,例如 home-mac-build。标签只是路由地址,不是授权证明。先写一个不带任何密钥的 smoke job:
name: Mac build smoke test
on:
workflow_dispatch:
jobs:
inspect:
runs-on: [self-hosted, macOS, ARM64, home-mac-build]
steps:
- run: uname -m && sw_vers -productVersion
- run: xcodebuild -version
手工点 Run workflow,确认 job 真的被这台 Mac 接走,且 Xcode 版本满足项目需要,再把正式构建 job 的 runs-on 切过来。只改标签、让 Runner 在 GitHub 页面显示 online,还不能证明项目能打包。需要 Node 的站点查 node 和 npm,需要 Xcode 的 App 查 SDK 和签名环境,需要 Docker 的网站镜像查 daemon 是否真的在跑。GitHub 的标签说明也强调,标签负责把 job 路由到符合条件的机器。
第三步,让 Runner 随系统稳定启动。macOS 的 Runner 可以安装为服务;服务启动后的 PATH 可能与我打开 Terminal 时不同,所以“我在命令行能找到 gh / docker”不等于后台 job 也能找到。安装成服务后,要从 Actions job 里再测一次。Mac 如果睡眠或断网,job 会排队,自动化不会替我把电脑唤醒。官方监控与排障说明列出了服务状态的检查方法。
一次真正的故障:Runner 在线,Docker 却不在线#
我的个人网站在自托管 Mac 上发布时,就碰到过很具体的反例。9 月 26 日一次发布的主线 CI 已经全绿,Buildx 却报:/var/run/docker.sock 不存在。Runner 在线,只说明接单进程活着;构建 Docker 镜像还得有 Docker daemon。启动本机 OrbStack 后,命令行里的 docker info 能通,但发布工作流为了隔离 GHCR 凭据使用临时 DOCKER_CONFIG,它把原来的 Docker context 也隔离掉了,Buildx 又去找默认 socket。最后是在隔离凭据前把当前 context 的 socket 明确传给 job,Buildx、镜像发布和生产部署才走完。
这次故障发生在网站仓库的自托管 Mac 发布链路上;它说明的是 Runner、构建工具和后台服务分别有自己的状态。我不把“机器在线”写成“构建环境已验证”。那次修复后的主线 CI、镜像构建、生产部署和公网路由检查都通过了,才算恢复。
为什么我只让它接自己信得过的活#
自托管 Runner 会以本机用户身份执行 workflow 中的命令。若一份陌生 PR 能让它跑任意脚本,那就等于把家里的开发机交给外部代码。我把这种机器留在受控的私有仓库,只让已经批准的分支和工作流接近部署凭据;测试 job 不带生产密钥,部署 job 单独使用受限制的 environment。GitHub 也明确建议自托管 Runner 优先用于私有仓库,并警告公开仓库的 fork PR 可能把危险代码带到自有机器上。官方安全说明值得在接线前读一遍。
所以我现在看一台 Mac mini 能不能当 Runner,会连问四个问题:它能稳定在线吗?项目依赖真的齐吗?它会执行谁提交的代码?失败之后能看见证据、清理临时凭据并重新跑吗?前两个决定“能否构建”,后两个决定“我敢不敢长期让它构建”。