在 VPS 上自托管 CI Runner:GitLab Runner / Forgejo Actions 私有构建集群(2026)

把 GitLab Runner 与 Forgejo Actions Runner 跑在自己的 VPS 上,省掉 SaaS 按分钟计费、用本地缓存加速构建、靠标签把任务调度到指定机器。给出 Docker 部署、缓存与并发调优、磁盘清理与多机扩展的实战步骤,适合想省构建费的开发者。

GitHub Actions 免费额度在缩水,GitLab.com 的共享 Runner 排队久、并发低,私有仓库一多分钟费蹭蹭涨。很多开发者没意识到:把 Runner 跑在自己的一台 VPS 上,构建分钟直接变「无限」,本地缓存还能把构建时间砍掉一大截。本文讲怎么在 VPS 上自托管两套主流方案——GitLab Runner 和 Forgejo Actions Runner,覆盖 Docker 部署、缓存加速、标签调度、磁盘清理和多机扩展,全是老站长踩过坑后的实操。

延伸阅读

更多相关攻略推荐:别被多核骗了!为什么买 VPS 必须看单核(Single-Core)【区块链节点 03】用 VPS 运行比特币全节点:UTXO、prun【VPS 自建影音与网盘 01】用 VPS 搭建私人影音服务器:JeMatrix / Element 自托管即时通讯:搭一个不被监控的私Immich / PhotoPrism 自建照片备份:把 Googl

一、为什么要在 VPS 上自托管 CI Runner

  • 分钟费归零:SaaS 的共享 Runner 按分钟计费,私有项目一多就是持续烧钱;自己的 Runner 跑在自己机器上,时长等于零成本。
  • 缓存加速:共享 Runner 每次 job 都是干净环境,node_modules、pip 缓存、Docker 层全得重拉;私有 Runner 把缓存留在本地磁盘,二次构建秒级复用。
  • 标签调度:给机器打 docker、gpu、arm 之类的标签,CI 里用 tags 把任务精准派到对应机器,比如重构建走高配、轻量 lint 走小机。
  • 定制环境:想装啥依赖装啥,私有镜像、内网域名、GPU 驱动,共享 Runner 上搞不了的这里随便搞。
  • 数据不出机器:测试用的数据库 dump、源码、产物都留在你控制的 VPS 上,合规和隐私更可控。

当然也有代价:机器是你自己的,运维也得自己扛——系统更新、磁盘清理、Runner 版本和 Git 平台对齐、偶尔的排障都要花时间。共享 Runner 这些都不用管,但代价是钱和排队。老站长的经验是:当你的团队每月 CI 分钟费开始「肉疼」,或者构建经常卡在 pending 等共享资源时,就是上私有 Runner 的信号。一台 2 vCPU 的小鸡往往就能接住一个小团队的日常构建,投入产出比很高。

一句话:只要你的 CI 跑得够频繁,一台月付几十块的小鸡,往往比 SaaS 分钟费还便宜,而且更快。

二、GitLab Runner 自托管:从装到跑

GitLab Runner 支持多种执行器,VPS 场景首推 docker executor——每个 job 在干净容器里跑,环境可复现,又能复用本机镜像层做缓存。最省事的是用 Docker 跑 Runner 本身:

docker run -d --name gitlab-runner --restart always   -v /srv/gitlab-runner/config:/etc/gitlab-runner   -v /var/run/docker.sock:/var/run/docker.sock   gitlab/gitlab-runner:latest

注册 Runner(GitLab 16.0+ 用认证 token,旧版用注册 token;在项目 Settings > CI/CD > Runners 里拿):

docker exec -it gitlab-runner gitlab-runner register   --non-interactive   --url "https://gitlab.com"   --token "YOUR_TOKEN"   --executor "docker"   --docker-image "alpine:latest"   --description "vps-docker-runner"   --tag-list "docker,build"   --run-untagged="true"

除了 docker executor,GitLab Runner 还有 shell(最快但隔离差,只在完全可信环境用)、docker+machine(按需开机器做弹性伸缩,适合云 API)、kubernetes 等。单台 VPS 场景 docker executor 是甜点区:既要隔离复现,又不用折腾集群。注册时如果图省事,也可以不加 --non-interactive,直接跑 gitlab-runner register 走交互式问答,逐项填 URL、token、executor、默认镜像即可。

注册完生成 /srv/gitlab-runner/config/config.toml,关键调优在下面几节。

三、并发与缓存调优(关键省钱点)

config.toml 里最该改的是 concurrent(同时跑的 job 数)和 Docker 的拉取策略、缓存挂载:

concurrent = 4
check_interval = 3

[[runners]]
  name = "vps-docker-runner"
  url = "https://gitlab.com"
  executor = "docker"
  [runners.docker]
    image = "alpine:latest"
    pull_policy = ["if-not-present"]
    volumes = ["/cache", "/var/run/docker.sock:/var/run/docker.sock"]
    shm_size = 0
  • concurrent 按你 VPS 的 vCPU 数来:4 核设 4 比较稳,别贪多把内存打满。
  • pull_policy = if-not-present:基镜像在本地就直接复用,不每次重新拉,省带宽也省时间。
  • 挂 /cache:配合 .gitlab-ci.yml 里的 cache 配置,把 node_modules、pip、.m2 这类依赖目录持久化到本机,二次构建飞快。
  • 想在本机构建并推送镜像,给 Docker 守护挂 socket(上面的 volumes 已挂),但注意这等于把宿主权限交出去,只在你完全掌控的私有 VPS 上才开。

这里有个容易忽略的点:concurrent 不是越大越好。每个并行 job 都要占一份 CPU、内存和磁盘 I/O,设太大表面吞吐高,实则互相抢资源、构建反而变慢,还可能 OOM 把 Runner 拖挂。稳妥做法是 concurrent 约等于 vCPU 数,内存按「单 job 峰值 × concurrent」预留余量;比如 4 核 8GB 的机器,单 job 峰值约 1.5GB,concurrent=4 正好卡在临界,想更稳就降到 3。调的时候盯构建时长和失败率,找到你这台机器的最优值再固化。

四、.gitlab-ci.yml 里的缓存与标签实战

缓存要在流水线里显式声明,并和标签配合把重活派到高配机器:

cache:
  key: "$CI_COMMIT_REF_SLUG"
  paths:
    - node_modules/
    - .next/cache/

build:
  image: node:20-alpine
  tags:
    - docker
    - build
  script:
    - npm ci
    - npm run build

没写 tags 的 job 可能落到共享 Runner 上吃掉你的配额,所以永远给 job 打 tag。缓存 key 用分支名,保证同分支复用、不同分支隔离。遇到 job 卡在 pending,十有八九是 tag 对不上——检查 .gitlab-ci.yml 的 tags 和 Runner 注册时的 tag-list 是否一致。

五、Forgejo Actions Runner:自建 Git 平台的 CI

Forgejo 是开源的 Git 平台(Gitea 分支),它的 Actions 兼容 GitHub Actions 语法,但需要你自己提供 Runner。官方 Runner 镜像在 code.forgejo.org/forgejo/runner,最稳的是用 Docker Compose 跑,且建议和 Forgejo 本体分开放不同机器(安全和性能)。先在 Forgejo 后台 Admin > Actions > Runners 点「Create new runner」拿到 secret,再起容器:

services:
  forgejo_runner:
    image: code.forgejo.org/forgejo/runner:latest
    command: ["tail", "-f", "/dev/null"]
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - ./data:/data

进容器注册并生成默认配置:

docker compose exec forgejo_runner forgejo-runner register
docker compose exec forgejo_runner forgejo-runner generate-config > ./data/config.yml

register 会问你的 Forgejo URL 和 secret,生成 /data/.runner;generate-config 产出 /data/config.yml。改 compose 的 command 成真正启动守护进程,并把宿主 docker 组 ID 加进去,让 Runner 能调度容器:

services:
  forgejo_runner:
    image: code.forgejo.org/forgejo/runner:latest
    command: "/bin/forgejo-runner --config /data/config.yml daemon"
    restart: always
    group_add:
      - 995
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - ./data:/data

日志里出现「Starting runner daemon」「declared successfully」「poller launched」就说明上线了。Forgejo 的工作流文件放在仓库的 .forgejo/workflows/ 目录,语法基本照抄 GitHub Actions。

六、Forgejo 工作流示例(构建并推送镜像)

下面是一个典型的 Forgejo Actions 工作流,复用 Docker 层做缓存:

on:
  push:
    branches: [main]
jobs:
  build:
    runs-on: docker
    steps:
      - uses: actions/checkout@v4
      - name: Build image
        run: docker build -t my-app:$GITHUB_SHA .
      - name: Push
        run: docker push my-app:$GITHUB_SHA

runs-on: docker 对应 Runner 默认带有的 docker 标签。想跑在宿主机而非容器里,可在 config.yml 的 runner 段设 labels 为 self-hosted:host,并在工作流写 runs-on: self-hosted。注意 Forgejo 默认容器里再起 Docker 需要 privileged 或 Docker-in-Docker,单台 VPS 私用场景下挂宿主 socket 最省资源。

七、磁盘清理:私有 Runner 的最大隐性坑

共享 Runner 用完即焚,私有 VPS 会一直累积:构建缓存、悬空镜像、停止的容器。不清理,磁盘几周就满,job 直接失败。写一个清理脚本挂到 cron 每天跑:

#!/bin/bash
docker system prune -a -f --filter "until=24h"
docker builder prune -f --filter "until=48h"
echo "cleanup done at $(date)" >> /var/log/ci-cleanup.log
  • 悬空镜像用 docker image prune,构建缓存用 docker builder prune
  • 只清 24 / 48 小时前的,避免正在用的层被误删。
  • 挂到 0 4 * * * 每天凌晨执行,避开构建高峰。
  • Contabo 这类大存储低价机适合当构建机,但再大也得清,否则 inode 和层堆积照样爆。

除了定时清理,还可以从根上减少堆积:把 Docker 的存储驱动和数据根目录挂到独立的大分区,别和系统等混在一起;流水线里只缓存真正「重且稳」的依赖(node_modules、pip、.m2、Docker 基础层),别把每次都变的中间产物塞进 cache,否则缓存本身越滚越大。监控磁盘要用 df -hdocker system df 看占用趋势,别等 job 报错才发现有问题。磁盘满导致的失败最难排查,因为它表现成各种诡异的「权限拒绝」或「写文件失败」,提前预防比事后救火省心得多。

八、多机扩展与标签调度集群

  • 水平扩展:再买一两台 VPS,按用途打不同标签,比如 docker-build 给高配、unit-test 给小机,CI 里用 tags 分流,队列几乎不堵。
  • 架构分离:GitLab 的控制器(Web/API)和 Runner 分开;Forgejo 本体和 Runner 分两台机器,互不抢资源也更安全。
  • 分布式缓存:机器超过一台,本地缓存就不够了,起一个 MinIO(S3 兼容)做统一缓存后端,让 node_modules 从内网拉而不是公网 registry。
  • 监控:Runner 自带 Prometheus 指标(队列长度、job 时长),接 Grafana 看排队和失败率,决定要不要再加机器。
  • 异构调度:GPU 机器打 gpu 标签专门跑模型训练 job,ARM 机器打 arm 标签跑交叉构建,省钱又精准。

九、VPS 选型与成本账

负载建议配置适合商家
轻量(前端、小项目)2 vCPU / 4-8GB / 50GB NVMeRackNerd 年付小机
中等(Docker 化后端)4 vCPU / 8-16GB / 100GB NVMeHetzner CPX、RackNerd
重度(ML、镜像构建)8+ vCPU / 32GB+ / 大存储ContaboHetzner CCX

算笔账:GitHub Actions 私有仓库 macOS/Linux 分钟费不便宜,一个月几百分钟就可能超过一台 Hetzner 2-4 欧小机的月费;构建越频繁,自托管越划算。带宽也要看:容器化工作流每天拉镜像、传产物,选 1TB+/月起步,重负载上 5-10TB 更稳。

#VPS自建 #CIRunner #GitLabRunner #Forgejo #私有CI #构建加速 #标签调度