ตั้ง GitHub Actions Self-hosted Runner บน VPS สำหรับ CI/CD

ตั้ง GitHub Actions Self-hosted Runner บน VPS

มิถุนายน 2026 · อ่าน 8 นาที

GitHub Actions self-hosted runner คือ agent ที่ติดตั้งบน VPS ของคุณเองเพื่อรัน workflow แทน GitHub-hosted runner — ไม่มีขีดจำกัด build minutes, เลือก OS/software ได้เสรี, เข้าถึง internal network และ deploy ได้โดยตรงโดยไม่ผ่าน cloud เหมาะสำหรับโปรเจกต์ที่ต้องการ build นาน, รัน test กับ database จริง หรือ deploy ไปยัง server ที่ไม่ได้เปิด internet บทความนี้สอนตั้ง runner ให้ปลอดภัย รัน docker และตั้ง systemd ให้ auto-start

ข้อดีของ Self-hosted Runner

สร้าง User แยกสำหรับ Runner (ความปลอดภัย)

# ไม่ควรรัน runner ด้วย root — สร้าง user แยก
useradd -m -s /bin/bash github-runner
usermod -aG docker github-runner   # ถ้าต้องใช้ Docker

# สร้าง directory สำหรับ runner
mkdir -p /opt/github-runner
chown github-runner:github-runner /opt/github-runner

ดาวน์โหลดและ Configure Runner

su - github-runner
cd /opt/github-runner

# ดาวน์โหลด runner (ตรวจเวอร์ชั่นล่าสุดได้จาก github.com/actions/runner/releases)
RUNNER_VERSION="2.316.1"
curl -sL "https://github.com/actions/runner/releases/download/v${RUNNER_VERSION}/actions-runner-linux-x64-${RUNNER_VERSION}.tar.gz" -o runner.tar.gz
tar xzf runner.tar.gz
rm runner.tar.gz

# Configure — ต้องการ registration token จาก GitHub
# ไปที่ repo → Settings → Actions → Runners → New self-hosted runner
# คัดลอก token มาใส่ด้านล่าง
./config.sh \
  --url https://github.com/YOUR_ORG/YOUR_REPO \
  --token YOUR_REGISTRATION_TOKEN \
  --name "vps-runner-01" \
  --labels "linux,x64,vps,production" \
  --work "_work" \
  --unattended

ตั้ง systemd Service ให้ Auto-start

# ออกจาก github-runner user กลับมาเป็น root
exit

# ติดตั้ง service (script มากับ runner)
cd /opt/github-runner
./svc.sh install github-runner
./svc.sh start

# ตรวจสอบสถานะ
./svc.sh status
systemctl status actions.runner.YOUR_ORG-YOUR_REPO.vps-runner-01.service

ตัวอย่าง Workflow ที่ใช้ Self-hosted Runner

# .github/workflows/deploy.yml
name: Deploy to VPS

on:
  push:
    branches: [main]

jobs:
  build-and-deploy:
    runs-on: [self-hosted, linux, vps]   # เลือก runner ด้วย labels

    steps:
      - uses: actions/checkout@v4

      - name: Build Docker image
        run: |
          docker build -t myapp:${{ github.sha }} .
          docker tag myapp:${{ github.sha }} myapp:latest

      - name: Deploy
        run: |
          docker-compose pull
          docker-compose up -d --no-build
          docker system prune -f
        env:
          DATABASE_URL: ${{ secrets.DATABASE_URL }}

      - name: Health check
        run: curl -f http://localhost:3000/health || exit 1

ตั้ง Runner สำหรับ Organization (ใช้หลาย Repo)

# แทนที่จะผูก runner กับ repo เดียว ผูกกับ organization แทน
# ไปที่ org → Settings → Actions → Runners → New self-hosted runner
# ใช้ organization URL แทน repo URL

./config.sh \
  --url https://github.com/YOUR_ORG \
  --token ORG_REGISTRATION_TOKEN \
  --name "vps-shared-runner" \
  --labels "linux,x64,shared"

# จำกัด runner ให้ใช้ได้เฉพาะ repo ที่กำหนด
# org → Settings → Actions → Runner groups
Tip: อย่าใช้ self-hosted runner กับ public repository โดยไม่มี protection — ทุกคนที่ fork repo สามารถรัน workflow และเข้าถึง server คุณได้ ตั้ง "Require approval for first-time contributors" ใน repo settings ก่อนเสมอ

ตรวจสอบว่า Runner ทำงานถูกต้อง

# ดูสถานะ systemd service
systemctl status actions.runner.*.service

# ดู log แบบ real-time
journalctl -u actions.runner.*.service -f

# ตรวจว่า runner ออนไลน์บน GitHub
# ไปที่ repo → Settings → Actions → Runners
# ต้องเห็นสถานะ "Idle" หรือ "Active"

# ทดสอบ trigger workflow ด้วย manual dispatch
# repo → Actions → เลือก workflow → Run workflow

# ตรวจ work directory หลัง run สำเร็จ
ls /opt/github-runner/_work/
Tip: runner log อยู่ที่ /opt/github-runner/_diag/ — ถ้า job ล้มเหลวดูไฟล์ล่าสุดในโฟลเดอร์นี้ก่อน เห็นทุก step ที่รันและ error message เต็ม

แก้ปัญหาที่พบบ่อย

Error: "Runner is already configured"

เกิดเมื่อ configure ซ้ำโดยไม่ remove runner เดิมก่อน

# Remove config เดิมก่อน
cd /opt/github-runner
./config.sh remove --token YOUR_REMOVAL_TOKEN

# หรือ remove แบบ force (กรณี token หมดอายุ)
rm -f .runner .credentials .credentials_rsaparams

Error: "Docker: permission denied" ใน workflow

User github-runner ไม่ได้อยู่ใน docker group

usermod -aG docker github-runner
# ต้อง logout/login ใหม่หรือ restart service
systemctl restart actions.runner.*.service

# ทดสอบ
su - github-runner -c "docker ps"

Runner ขึ้น "Offline" บน GitHub ทั้งที่ service รัน

มักเกิดจาก token หมดอายุหรือ firewall block outbound HTTPS

# ตรวจ connectivity ไปยัง GitHub
curl -I https://api.github.com
curl -I https://github.com

# ตรวจ firewall
ufw status
# อนุญาต outbound 443 ถ้าถูกบล็อก
ufw allow out 443/tcp

Error: "No space left on device" ระหว่าง build

Docker image สะสมจน disk เต็ม ต้องตั้ง cleanup อัตโนมัติ

# Cleanup ด้วยมือ
docker system prune -af --volumes

# ตั้ง cron ให้ cleanup ทุกคืน
crontab -e -u github-runner
# เพิ่ม:
# 0 2 * * * docker system prune -f --filter "until=24h"

คำถามที่พบบ่อย

Self-hosted runner ปลอดภัยไหม?

ปลอดภัยถ้าตั้งให้ถูกต้อง — รันด้วย non-root user แยก, ใช้กับ private repo เท่านั้น, ตั้ง firewall ให้ runner เข้าถึง resource เฉพาะที่ต้องการ อย่าใช้กับ public repo เด็ดขาดเพราะ PR จากคนนอกสามารถรัน code บน server คุณได้

Runner ตัวเดียวรัน job พร้อมกันได้ไหม?

Runner ตัวเดียวรัน job ทีละงาน ถ้าต้องการ parallel ต้องติดตั้ง runner หลายตัวบน server เดียวกัน (ต่างชื่อกัน) หรือตั้ง max-parallel ใน workflow เพื่อควบคุม concurrency

Secrets ใน GitHub Actions ปลอดภัยแค่ไหน?

Secrets ถูก encrypt และส่งมายัง runner แบบ environment variable ในระหว่าง job — ไม่ถูก log และถูก mask ใน output อัตโนมัติ แต่ code ใน workflow ที่รันบน runner ยังสามารถเข้าถึง secrets ได้ผ่าน ${{ secrets.NAME }} ดังนั้นให้ review workflow ทุก PR ก่อน merge