Self-host GitHub Actions runner trên VPS: tiết kiệm CI minutes

Mục lục
TL;DR
  • Self-host GitHub Actions runner trên VPS giúp tiết kiệm 2000 phút CI/tháng miễn phí của GitHub Free, và unlimited cho repo private trả phí.
  • Setup chỉ 15 phút trên VPS Ubuntu/AlmaLinux. Runner chạy như service systemd, tự restart khi reboot.
  • Phù hợp khi build pipeline nặng (Docker, monorepo, image multi-arch) hoặc cần network nội bộ truy cập DB private.
  • Cảnh báo bảo mật: KHÔNG dùng self-host runner cho public repo (rủi ro PR độc hại chạy code trên server bạn).
  • Bài này hướng dẫn full: register runner, chạy với non-root, sử dụng label, scale với docker, cleanup workspace, monitor.

GitHub Actions là CI/CD tốt nhất thị trường năm 2026 cho dev solo và team nhỏ. Nhược điểm duy nhất là quota: free plan chỉ 2000 phút build/tháng cho repo private trên Linux runner mặc định. Khi project lớn lên với pipeline lint, test, build image Docker đa kiến trúc, deploy staging, một push merge có thể tiêu 15-20 phút. Vài lần PR mỗi ngày là hết quota giữa tháng.

Self-host runner trên VPS giải quyết đúng nỗi đau này. Mỗi job CI chạy trên server bạn sở hữu, không tính vào quota GitHub. Đổi lại bạn phải tự lo máy chủ, bảo mật, cleanup workspace. Bài này hướng dẫn từ A đến Z trên Cloud VPS 50/80 của TND: register runner, chạy non-root, gắn label phân loại workload, scale lên 3-5 runner song song, và checklist bảo mật bắt buộc.

Khi nào nên self-host và khi nào không?

Self-host runner đáng đầu tư khi bạn rơi vào ít nhất 2 trong các tình huống:

  • Quota 2000 phút free hoặc 3000 phút Pro không đủ, đang phải trả thêm 0.008 USD/phút.
  • Pipeline build Docker image nặng (>500MB layer), tốn 3-8 phút mỗi lần build.
  • Cần test integration với database, Redis, Kafka chạy trên VPS nội bộ.
  • Cần caching dependencies giữa các job (npm, pip, go mod) mà cache GitHub Actions không đủ nhanh.
  • Cần build image multi-arch (amd64 + arm64) yêu cầu QEMU và tốn 10-15 phút.
  • Cần network whitelist IP để gọi API nội bộ của công ty.

KHÔNG nên self-host khi:

  • Repo public, có nguy cơ PR từ contributor không tin cậy chạy code arbitrary trên runner.
  • Workflow đơn giản (lint + test 1-2 phút), không vượt quá quota free.
  • Không có ai monitor server thường xuyên.
  • Không sẵn sàng setup firewall, fail2ban, SSH key, security update.

Yêu cầu VPS cho runner

Use casevCPURAMDiskĐề xuất gói TND
1 runner, build nhẹ (Node, Python)24 GB40 GBCloud VPS 50
2-3 runner, build Docker48 GB80 GBCloud VPS 80
5+ runner, build matrix, multi-arch816 GB160 GBCloud VPS 160
Monorepo lớn, build C++/Rust1632 GB320 GBCloud VPS 320+

Mẹo tiết kiệm: disk bị tốn nhanh do Docker layer, npm cache, build artifact. Snapshot tuần một lần và setup cron tự dọn dẹp là bắt buộc.

Bước 1: Tạo user runner riêng

Không bao giờ chạy runner bằng user root. Tạo user dedicated:

sudo adduser --disabled-password --gecos "" gh-runner
sudo usermod -aG docker gh-runner
sudo mkdir -p /opt/actions-runner
sudo chown gh-runner:gh-runner /opt/actions-runner
sudo -iu gh-runner

Nếu pipeline cần Docker, user gh-runner phải nằm trong group docker. Nếu cần sudo cho công việc đặc thù (rất hiếm), thêm vào sudoers với câu lệnh cụ thể, KHÔNG cho NOPASSWD ALL.

Bước 2: Lấy token và download runner

Trên GitHub, vào Settings của repo (hoặc org/enterprise nếu cần share runner) > Actions > Runners > New self-hosted runner > chọn Linux x64. Trang sẽ hiện token download và command đăng ký. Copy token (dạng ALPHANUM_15chars).

Trên VPS, với user gh-runner đã login:

cd /opt/actions-runner
curl -o actions-runner-linux-x64-2.319.0.tar.gz -L 
  https://github.com/actions/runner/releases/download/v2.319.0/actions-runner-linux-x64-2.319.0.tar.gz
tar xzf ./actions-runner-linux-x64-2.319.0.tar.gz
./config.sh --url https://github.com/YOUR_USER/YOUR_REPO --token YOUR_TOKEN 
  --name vps-runner-01 
  --labels self-hosted,linux,x64,vn,docker 
  --work _work 
  --unattended

Lúc này runner đã đăng ký với GitHub. Trên dashboard GitHub thấy runner "vps-runner-01" trạng thái Idle, label tự đặt.

Bước 3: Cài runner thành systemd service

Để runner tự khởi động khi reboot và restart khi crash:

sudo /opt/actions-runner/svc.sh install gh-runner
sudo /opt/actions-runner/svc.sh start
sudo systemctl status actions.runner.YOUR_USER-YOUR_REPO.vps-runner-01

Khi thấy "active (running)", runner đã sẵn sàng. Test bằng cách push một commit nhỏ, workflow nào dùng runs-on: self-hosted sẽ chạy trên VPS của bạn.

Bước 4: Sửa workflow dùng self-host runner

Trong .github/workflows/ci.yml, đổi runs-on:

name: CI
on:
  push:
    branches: [main, dev]
  pull_request:
jobs:
  test:
    runs-on: [self-hosted, linux, vn]
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
      - run: npm ci
      - run: npm test
  docker-build:
    runs-on: [self-hosted, linux, docker]
    steps:
      - uses: actions/checkout@v4
      - run: docker build -t app:latest .

Label "vn" và "docker" giúp routing: job test chạy bất kỳ runner nào có label vn, job build chỉ chạy runner có docker installed. Khi bạn có 3-5 runner với label khác nhau, GitHub tự phân phối hợp lý.

Bảo mật bắt buộc: 7 điều phải làm

  1. Repo private only: Trong Settings > Actions > General, bật "Disable for outside collaborators" và "Require approval for first-time contributors".
  2. Restrict workflow permissions: Đặt default GITHUB_TOKEN permission là read-only, từng workflow cần write thì khai báo riêng.
  3. Firewall: chỉ cho phép outbound đến github.com, ghcr.io, registry-1.docker.io. Đóng port không cần thiết.
  4. Không expose runner ra internet: runner pull job từ GitHub qua long-poll, không cần inbound port nào.
  5. fail2ban + SSH key only: chặn brute force SSH, disable password auth.
  6. Update VPS hàng tuần: apt update && apt upgrade -y trong cron Chủ nhật.
  7. Secret rotation: đổi GitHub PAT, runner registration token mỗi 90 ngày.

Cleanup workspace tự động

Mỗi job để lại file build, Docker layer, node_modules trong /opt/actions-runner/_work. Một tháng có thể chiếm 20-50 GB. Cron dọn dẹp:

# /etc/cron.d/runner-cleanup
0 4 * * * gh-runner find /opt/actions-runner/_work -mindepth 2 -maxdepth 2 -type d -mtime +7 -exec rm -rf {} +
0 5 * * 0 root docker system prune -af --volumes

Lệnh đầu xóa workspace cũ hơn 7 ngày của runner. Lệnh sau dọn Docker image dangling và volume orphan vào Chủ nhật 5 giờ sáng. Combo này giữ disk ổn định.

Scale lên nhiều runner song song

Một runner = một job tại một thời điểm. Để chạy 3 job song song, cần 3 runner. Cách đơn giản nhất: lặp lại bước 1-3 với tên khác (vps-runner-02, 03). Mỗi runner cần thư mục riêng:

sudo mkdir -p /opt/actions-runner-02
sudo chown gh-runner:gh-runner /opt/actions-runner-02
sudo -iu gh-runner
cd /opt/actions-runner-02
# Lặp lại tar và config.sh với tên vps-runner-02

Trên VPS 8GB RAM, chạy 3-4 runner đồng thời ổn. Vượt quá 4 runner thường gặp tranh chấp CPU và disk I/O, lại chậm hơn.

Cách nâng cao: dùng Docker để isolate mỗi job

Nếu lo lắng job lưu lại file độc hại giữa các lần chạy, dùng image runner ephemeral. Mỗi job chạy trong container mới, kết thúc thì xóa. Có 3 dự án phổ biến: actions-runner-controller (cho Kubernetes), myoung34/docker-github-actions-runner, summerwind/actions-runner.

Ví dụ docker compose chạy 3 runner ephemeral:

services:
  runner:
    image: myoung34/github-runner:latest
    environment:
      REPO_URL: https://github.com/YOUR_USER/YOUR_REPO
      ACCESS_TOKEN: $YOUR_GH_PAT
      RUNNER_NAME_PREFIX: docker-runner
      EPHEMERAL: "true"
      DISABLE_AUTO_UPDATE: "true"
      LABELS: self-hosted,linux,docker,ephemeral
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    restart: always
    deploy:
      replicas: 3

Mỗi container nhận 1 job, kết thúc thì exit. Docker compose tự restart, đăng ký runner mới. Workspace luôn sạch. Đổi lại tốn thêm 30 giây mỗi job cho việc start container.

Caching dependency tăng tốc build

Self-host runner cho phép cache filesystem-level, nhanh hơn cache GitHub Actions remote nhiều lần. Mount thư mục cache cố định:

- name: Setup Node with local cache
  run: |
    export NPM_CONFIG_CACHE=/home/gh-runner/.npm-cache
    npm ci
- name: Setup pip cache
  run: |
    export PIP_CACHE_DIR=/home/gh-runner/.pip-cache
    pip install -r requirements.txt

Lần đầu cài mất 1-2 phút. Lần sau chỉ 5-10 giây vì cache đã có sẵn trên disk. Docker buildx cũng dùng được local cache:

- uses: docker/setup-buildx-action@v3
- uses: docker/build-push-action@v6
  with:
    context: .
    cache-from: type=local,src=/tmp/.buildx-cache
    cache-to: type=local,dest=/tmp/.buildx-cache,mode=max

Monitor runner: dashboard và alert

Runner đôi khi treo hoặc disconnect. Setup monitor đơn giản bằng cron + curl:

#!/bin/bash
# /usr/local/bin/check-runner.sh
RUNNER_NAME=vps-runner-01
STATUS=$(curl -s -H "Authorization: token $GH_PAT" 
  https://api.github.com/repos/YOUR_USER/YOUR_REPO/actions/runners 
  | jq -r ".runners[] | select(.name=="$RUNNER_NAME") | .status")
if [ "$STATUS" != "online" ]; then
    curl -s -X POST "https://api.telegram.org/bot$BOT_TOKEN/sendMessage" 
      -d "chat_id=$CHAT_ID&text=Runner $RUNNER_NAME is $STATUS"
    systemctl restart actions.runner.YOUR_USER-YOUR_REPO.$RUNNER_NAME
fi

Cron mỗi 5 phút. Khi runner offline, Telegram báo và systemd restart. Trong 6 tháng vận hành, kịch bản này phục hồi 90 phần trăm sự cố mà không cần can thiệp.

Chi phí so sánh thực tế

Phương ánBuild minute/thángChi phí
GitHub Free runner2000 (Linux x64)0 (đã bao gồm)
GitHub Pro runner thêm5000 extra40 USD
GitHub larger runner 4-core300072 USD
Self-host Cloud VPS 50Unlimited (43200 phút máy)~25 USD
Self-host Cloud VPS 80Unlimited 2-3 runner song song~40 USD

Khi pipeline cần trên 3000 phút/tháng, self-host rẻ hơn rõ rệt. Cộng thêm latency build container về registry trong nước nhanh hơn ra GitHub Cloud nhiều, build Docker push lên ghcr.io từ VPS VN thường ổn 30-60 MB/s.

Khi runner gặp lỗi: 5 checklist debug

  1. Job stuck Queued: kiểm tra runner online trên GitHub Settings > Actions > Runners. Nếu offline, restart service.
  2. "No runners online with the requested labels": kiểm tra label trong workflow khớp label runner. Đặc biệt chữ hoa/thường.
  3. Permission denied khi docker build: user gh-runner chưa trong group docker, hoặc cần logout/login lại.
  4. Out of disk: chạy df -h, dọn _work cũ và docker prune. Cân nhắc nâng disk VPS.
  5. Build chậm bất thường: top xem CPU, nếu cao do nhiều runner chia nhau. Giảm số runner hoặc nâng VPS.

FAQ

Self-host runner có an toàn cho repo private nội bộ công ty không?

An toàn nếu tuân thủ 7 checklist bảo mật ở trên. Quan trọng nhất: không cho phép ai ngoài đội tin cậy chạy workflow (chặn outside collaborator, require approval), và giới hạn quyền GITHUB_TOKEN. Nhiều startup VN dùng self-host runner trong production CI nhiều năm không sự cố.

Có cần GitHub PAT để register runner không?

Không, registration token (token một lần) là đủ cho repo-level runner. Nếu muốn auto-register runner cho nhiều repo trong org, dùng PAT có scope admin:org để gọi API và tạo registration token tự động.

Khi runner upgrade phiên bản, cần làm gì?

Runner tự update mặc định mỗi khi GitHub publish version mới. Nếu muốn pin version, đặt --disableupdate khi config.sh. Manual upgrade: stop service, tải binary mới, replace, start service. Mất 1-2 phút, không ảnh hưởng job đang chạy nếu đợi runner idle.

Có thể share một runner cho nhiều repo trong org không?

Có. Đăng ký runner ở level org (Settings của org > Actions > Runners). Sau đó workflow trong bất kỳ repo nào của org đều dùng được. Nên dùng combo này khi có nhiều repo nhỏ, mỗi repo không đủ workload để run dedicated.

Runner self-host có hỗ trợ Windows và macOS không?

Có cho Windows (Server hoặc desktop), macOS thì cần máy Mac thật vì Apple cấm chạy macOS trong VM. Với mobile dev build iOS, thường thuê Mac mini cloud (MacStadium) làm self-host runner. Cho web/backend dev Việt Nam, runner Linux trên VPS đủ phục vụ.

Cloud VPS cho vibe coder

VPS chuyên cho CI/CD self-host: nhiều CPU, băng thông quốc tế ổn định

Cloud VPS TND sẵn AlmaLinux 9, Ubuntu 22/24, Debian 12/13. SSD CEPH, snapshot 1-click, backup hằng ngày, network 200Mbps trong nước. Băng thông quốc tế ổn cho việc pull image base từ Docker Hub, ghcr.io, push artifact lên registry private.

Xem 8 cấu hình Cloud VPS →

Chia sẻ bài viết