GitHub Actions 任务一直排队、托管 Runner 分钟数不够,或者构建必须访问内网数据库时,很多人第一反应是买台 VPS 跑自托管 Runner。
这条路确实能省钱,也能让构建环境完全可控。但它不是“装完就不管”的免费算力:工作流里的代码会直接在你的 VPS 上执行。Runner 能读到什么、连到哪里、是否能操作 Docker,决定了出问题时会影响多大。
这篇只讲一件事:用一台 Ubuntu/Debian VPS,把 GitHub Actions self-hosted Runner 安装、服务化并管稳。
如果项目公开、任务不多,而且没有内网访问需求,我会先用 GitHub 托管 Runner。它每次提供相对干净的环境,机器维护也不用自己操心。
自托管 Runner 更适合下面几种情况:
- 私有仓库构建时间长,想减少托管分钟消耗;
- Docker 镜像大,希望复用本地层缓存;
- 构建要访问 VPN、内网服务或固定出口 IP;
- 需要特殊 CPU 架构、系统依赖或硬件;
- 已经有一台空闲 VPS,愿意承担安全和运维成本。
如果你的真实需求是“在 GitHub 托管 Runner 构建,再把镜像部署到 VPS”,看这篇从 VPS 手动编译迁移到 GitHub Actions + Docker更合适。本文讲的是反过来:让 VPS 本身成为执行构建任务的 Runner。
Runner 程序本身很轻,真正吃资源的是你的编译、测试和 Docker 构建。
| 用途 | 建议起步配置 | 磁盘建议 |
|---|---|---|
| Shell、Go、小型 Node 测试 | 2 核 2GB | 30GB SSD |
| Node/Java 构建、普通 Docker 镜像 | 2–4 核 4GB | 60GB SSD |
| 多阶段镜像、大型前端或并行测试 | 4 核 8GB+ | 100GB+ SSD |
磁盘通常比内存更早爆。_work、Docker layer、工具缓存和日志都会越积越多。选机器前可参考开发测试用 VPS 配置清单,别只看月租。
下面以 Ubuntu/Debian 为例:
sudo adduser --disabled-password --gecos "" github-runner
sudo apt update
sudo apt install -y curl ca-certificates git jq tar
sudo install -d -o github-runner -g github-runner /home/github-runner/actions-runner
Runner 一般只需要主动连接 GitHub 的 HTTPS 443 端口,不需要为它单独开放公网入站端口。如果 VPS 配了严格的出站防火墙或代理,再按 GitHub 的通信域名清单放行。
不要从旧博客抄 Runner 版本号和校验值。进入仓库:
Settings → Actions → Runners → New self-hosted runner
选择 Linux 和实际架构后,GitHub 会生成下载、校验、配置命令。切换到专用用户,在安装目录执行页面上的当前命令:
sudo -iu github-runner
cd ~/actions-runner
#在这里执行 GitHub 页面当前显示的 curl、sha256sum 和 tar 命令
./config.sh \
--url https://github.com/OWNER/REPOSITORY \
--token REGISTRATION_TOKEN \
--name vps-runner-01 \
--labels docker \
--work _work
REGISTRATION_TOKEN 是 GitHub 临时生成的注册令牌,官方目前说明有效期为 1 小时。它不是长期 Secret,不要写进脚本、截图或文章。过期就回设置页重新生成。
配置成功后,Runner 默认会带上 self-hosted、操作系统和架构标签;这里额外加了 docker。标签只是路由条件,不会自动安装 Docker,也不会验证机器真的具备对应能力。
先退出专用用户,再在 Runner 目录安装服务:
exit
cd /home/github-runner/actions-runner
sudo ./svc.sh install github-runner
sudo ./svc.sh start
sudo ./svc.sh status
svc.sh 会创建 actions.runner.*.service。想看实时日志:
sudo journalctl -u 'actions.runner.*' -f
在 GitHub 的 Runners 页面看到 Idle,说明它已连接并等待任务;Active 表示正在跑任务;Offline 通常是服务没启动、机器断网或无法连接 GitHub。
新建 .github/workflows/self-hosted-test.yml:
name: self-hosted-runner-test
on:
workflow_dispatch:
jobs:
test:
runs-on: [self-hosted, linux, x64, docker]
steps:
- uses: actions/checkout@v4
- run: |
uname -a
docker version
数组里的每个标签都必须同时匹配一台在线 Runner。任务一直 Queued 时,先核对大小写、架构和自定义标签,再检查这台 Runner 是否允许当前仓库使用。
如果 VPS 是 ARM64,把 x64 改成 Runner 页面显示的 ARM64,不要为了让任务跑起来随便伪造标签。
如果工作流需要构建镜像,先按 Docker 官方方式安装 Docker Engine,再把 Runner 用户加入 docker 组:
sudo usermod -aG docker github-runner
sudo ./svc.sh stop
sudo ./svc.sh start
注意:能访问 /var/run/docker.sock,通常就等于能获得宿主机 root 级控制。工作流可以挂载宿主机目录、读取其他容器环境变量,甚至启动特权容器。
所以我不会把生产数据库、业务容器和 Runner 混在同一台 VPS。至少做到:
- 只服务可信的私有仓库;
- Runner 使用独立 VPS 或独立虚拟机;
GITHUB_TOKEN权限默认设为只读,按 Job 最小化开放;- 第三方 Action 固定到可信版本,关键场景固定 commit SHA;
- 不让未经审核的 fork PR 落到持久化 Runner;
- Runner 所在网络不要直接访问生产内网。
GitHub 也明确提醒:公开仓库的 fork 可以通过拉取请求提交恶意工作流,自托管 Runner 几乎不应直接用于公开仓库。
这三个概念很容易混:
actions/cache用来复用依赖或构建缓存;即使使用自托管 Runner,缓存仍保存到 GitHub 管理的云存储;- Artifact 用来保存一次工作流的结果,例如测试报告、压缩包和二进制文件;
- Runner 本地
_work、Docker layer、npm 或 Maven 缓存留在 VPS 磁盘,速度快,但会持久化,也可能被后续任务污染。
想要可重复构建,就把本地缓存当加速项,不要当唯一副本。Docker 生产环境的 healthcheck、日志和回滚思路可参考Docker Compose 生产配置指南。
每周至少看一次:
df -h
du -sh /home/github-runner/actions-runner/_work
docker system df
journalctl -u actions.runner.* --disk-usage
不要看到磁盘满了就直接执行 docker system prune -af。它会删除未使用容器、网络、镜像和构建缓存;如果再加 --volumes,还可能删掉未使用卷。
比较稳的顺序是:确认当前没有 Job → 删除确定不需要的旧工作目录 → 用 docker builder prune 清理可重建的构建缓存 → 再按清单处理旧镜像。重要数据先按备份恢复演练指南验证能恢复。
Runner 默认会自动更新:有任务时发现新版本会更新,长时间没任务也会在发布后一周内尝试更新。
持久化 VPS 建议保留默认自动更新。如果注册时用了 --disableupdate,GitHub 要求在新版本发布后 30 天内完成升级;遇到关键安全更新,旧版本可能更早停止接收任务。
真正该备份的不是 Runner 二进制,而是下面这些可重建信息:
- Runner 归属的仓库或组织;
- 用户、目录、labels 和工作目录约定;
- Docker 与构建依赖安装步骤;
- 防火墙和网络策略;
- 监控、磁盘清理与重建脚本。
把 VPS 做坏后能在半小时内重装,比给 _work 做整盘备份更实用。
cd /home/github-runner/actions-runner
sudo ./svc.sh status
sudo journalctl -u 'actions.runner.*' -n 200 --no-pager
sudo -u github-runner ./config.sh --check \
--url https://github.com/OWNER/REPOSITORY \
--pat YOUR_FINE_GRAINED_PAT
--check 的详细结果会写进安装目录的 _diag。不要为了省事长期关闭 TLS 验证。
检查 Runner 是否 Idle、所有 runs-on 标签是否匹配、仓库是否有 Runner 使用权限。如果同一台机器只有一个 Runner 进程,它一次通常只能处理一个 Job,前一个没结束时后面的任务只能排队。
先到 Actions 页面确认 Job 是否真的还在执行,再看 _diag/Runner_*.log 和 _diag/Worker_*.log。如果是失控的 Docker 构建,先保留日志和进程信息,确认影响后再停止服务,不要直接重启整台生产机。
sudo -u github-runner docker version
id github-runner
ls -l /var/run/docker.sock
确认用户组后重启 Runner 服务,让新组权限生效。仍失败就检查 rootless Docker 或 Socket 路径,而不是把服务改成 root。
在 GitHub 的 Runner 详情页点 Remove,页面会生成限时移除令牌。然后在安装目录按页面命令执行:
sudo ./svc.sh stop
sudo ./svc.sh uninstall
sudo -u github-runner ./config.sh remove --token REMOVE_TOKEN
机器已经损坏时,可以在 GitHub 页面强制移除。普通持久化 Runner 连续 14 天未连接也会被自动移除,但别把这个机制当清理流程。
计算任务运行在自己的机器上,但缓存、Artifact、网络和其他 GitHub 服务仍可能受套餐与计费规则影响。省下的是托管计算资源,不等于所有 Actions 成本都归零。
可以,每个 Runner 使用独立目录、名称和服务。但它们会争抢 CPU、内存、磁盘和 Docker Daemon。小机器更适合先装一个,通过 Job 并发和排队数据再决定是否扩容。
技术上可以,安全上要谨慎。更稳的做法是构建 Runner 只产出签名镜像或 Artifact,部署阶段再经过环境审批和权限更小的发布身份。
准备长期运行前,再做一遍VPS 安全加固清单和VPS 监控告警配置。自托管 Runner 最值钱的不是“免费算力”,而是可控;如果权限、磁盘和重建流程也可控,它才真的省时间。
