Self-hosted Runner: Kendi Sunucunda GitHub Actions Kurulumu
GitHub Actions’ı ilk keşfettiğimde aklıma gelen ilk soru şu olmuştu: “Peki ya kendi sunucumu kullanabilir miyim?” GitHub’ın sağladığı hosted runner’lar çoğu senaryo için yeterli, ancak özel ağ gereksinimleri, performans ihtiyaçları veya maliyet optimizasyonu söz konusu olduğunda self-hosted runner kavramı hayat kurtarıcı hale geliyor. Bu yazıda sıfırdan bir self-hosted runner kurulumunu, güvenlik pratiklerini ve gerçek dünya senaryolarını ele alacağız.
Self-Hosted Runner Neden Kullanılır?
GitHub’ın ücretsiz planında aylık 2000 dakika CI/CD süresi veriliyor. Aktif bir ekiple çalışıyorsanız bu süre hızla tükeniyor. Ama asıl mesele sadece maliyet değil.
Şirket içi bir sistemle entegrasyon kuruyorsunuz diyelim. Veritabanınız dışarıya açık değil, VPN arkasında. GitHub’ın kendi runner’larından bu sisteme erişmenin yolu yok. Ya da GPU gerektiren ML model eğitimi pipeline’ınız var ve her build için özel donanım istiyorsunuz. İşte tam bu noktalarda self-hosted runner devreye giriyor.
Kısaca özetlersek neden tercih edildiğini:
- Özel ağ erişimi: Şirket içi servisler, veritabanları, artifact repository’ler
- Maliyet kontrolü: Kendi sunucunuzu kullanarak GitHub dakika limitini aşmama
- Performans: Özelleştirilmiş donanım, daha fazla CPU/RAM/disk
- Önceden kurulu araçlar: Özel yazılımlar, lisanslı araçlar, şirket sertifikaları
- Compliance gereksinimleri: Kodun dışarıya çıkmaması gereken durumlar
- Uzun süreli build’ler: GitHub hosted runner’larda 6 saatlik limit var, self-hosted’da bu kısıt yok
Kurulum Öncesi Hazırlık
Runner kuracağımız sunucunun bazı temel gereksinimleri karşılaması gerekiyor. Ben bu örnek için Ubuntu 22.04 LTS üzerinde çalışacağım.
# Sistem güncellemelerini yap
sudo apt update && sudo apt upgrade -y
# Temel bağımlılıkları kur
sudo apt install -y curl wget git unzip build-essential
# Runner için ayrı bir kullanıcı oluştur (root çalıştırma!)
sudo useradd -m -s /bin/bash github-runner
sudo usermod -aG sudo github-runner
# Runner dizinini oluştur
sudo mkdir -p /opt/github-runner
sudo chown github-runner:github-runner /opt/github-runner
Önemli not: Runner’ı asla root kullanıcısıyla çalıştırma. Güvenlik açısından ayrı bir kullanıcı oluşturmak kritik. Birisi repo’ya kötü amaçlı bir workflow dosyası push ederse, runner’ın sisteme verebileceği zarar minimize edilmiş olur.
GitHub’dan Token Alma ve Runner Kurulumu
GitHub arayüzünden token almak için şu yolu izle:
- Repo sayfası > Settings > Actions > Runners > New self-hosted runner
Burada hem token’ı hem de kurulum komutlarını göreceksin. Ben kurulum adımlarını biraz daha detaylı ele alacağım:
# github-runner kullanıcısına geç
sudo su - github-runner
cd /opt/github-runner
# Runner paketini indir (versiyonu GitHub'dan kontrol et, güncel sürümü kullan)
curl -o actions-runner-linux-x64-2.317.0.tar.gz -L
https://github.com/actions/runner/releases/download/v2.317.0/actions-runner-linux-x64-2.317.0.tar.gz
# SHA256 doğrulaması yap
echo "9e883d210df8c6028aff475475a457d380353f9d01877d51cc01a17b2a91161d
actions-runner-linux-x64-2.317.0.tar.gz" | shasum -a 256 -c
# Paketi aç
tar xzf ./actions-runner-linux-x64-2.317.0.tar.gz
# Runner'ı yapılandır
./config.sh --url https://github.com/KULLANICI/REPO
--token BURAYA_TOKEN_YAZ
--name "production-runner-01"
--labels "self-hosted,linux,production,ubuntu-22.04"
--work /opt/github-runner/_work
Yapılandırma sırasında birkaç soru soruyor. Runner grubunu varsayılan bırakabilirsin, isim ve etiketler için kendi değerlerini gir. Etiketler workflow’larda runner seçiminde kullanılacak, bu yüzden anlamlı isimler ver.
Systemd Servisi Olarak Çalıştırma
Runner’ı manuel başlatmak yerine systemd servisi olarak ayarlamak çok daha sağlıklı. Sunucu yeniden başladığında otomatik devreye girer.
# Servis kurulumunu runner dizininden yap (root ile)
sudo /opt/github-runner/svc.sh install github-runner
# Servisi başlat
sudo systemctl start actions.runner.KULLANICI-REPO.production-runner-01
# Otomatik başlatmayı aktif et
sudo systemctl enable actions.runner.KULLANICI-REPO.production-runner-01
# Durumu kontrol et
sudo systemctl status actions.runner.KULLANICI-REPO.production-runner-01
Servis adı biraz uzun görünüyor, bunu bir değişkene atayabilirsin ya da alias tanımlayabilirsin. Logları görmek için:
# Servis loglarını takip et
sudo journalctl -u actions.runner.KULLANICI-REPO.production-runner-01 -f
# Son 100 satırı göster
sudo journalctl -u actions.runner.KULLANICI-REPO.production-runner-01 -n 100
Runner online olduktan sonra GitHub arayüzünde yeşil nokta ile “Idle” durumunda görünmesi gerekiyor. Bu noktaya geldiysen kurulum başarılı demektir.
İlk Workflow Dosyası
Runner hazır, şimdi bunu kullanan bir workflow yazalım. Basit bir Node.js uygulaması deploy eden örnek:
# .github/workflows/deploy.yml
name: Deploy to Production
on:
push:
branches:
- main
workflow_dispatch:
jobs:
build-and-deploy:
runs-on: [self-hosted, linux, production]
steps:
- name: Kodu checkout et
uses: actions/checkout@v4
- name: Node.js kur
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Bağımlılıkları yükle
run: npm ci
- name: Test çalıştır
run: npm test
- name: Build al
run: npm run build
env:
NODE_ENV: production
- name: Uygulamayı deploy et
run: |
pm2 stop myapp || true
pm2 delete myapp || true
pm2 start dist/index.js --name myapp
pm2 save
runs-on satırına dikkat et. Hem self-hosted hem de tanımladığımız özel etiketleri veriyoruz. Bu sayede birden fazla runner varsa doğru olanı seçiyor.
Gerçek Dünya Senaryosu: Özel Ağda Veritabanı Migrasyonu
Diyelim ki üretim veritabanın dışarıya açık değil ve her deploy’da migration çalıştırman gerekiyor. Runner sunucun bu veritabanına erişebiliyor ama GitHub’ın sunucuları erişemiyor. Bu senaryo self-hosted runner’ın en güçlü olduğu alan.
# .github/workflows/deploy-with-migration.yml
name: Deploy with Database Migration
on:
push:
branches:
- main
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Unit testleri çalıştır
run: |
npm ci
npm test
deploy:
needs: test
runs-on: [self-hosted, linux, production]
environment: production
steps:
- uses: actions/checkout@v4
- name: Bağımlılıkları yükle
run: npm ci --production
- name: Veritabanı migration çalıştır
run: npx prisma migrate deploy
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
- name: Uygulamayı güncelle
run: |
pm2 reload myapp --update-env
env:
NODE_ENV: production
API_SECRET: ${{ secrets.API_SECRET }}
- name: Health check
run: |
sleep 10
curl -f http://localhost:3000/health || exit 1
Burada test job’ı GitHub’ın kendi runner’larında, deploy job’ı ise senin sunucunda çalışıyor. Bu yaklaşım hem maliyet optimizasyonu sağlıyor hem de her işin en uygun ortamda çalışmasını garantileyiyor.
Runner Grupları ve Organizasyon Düzeyinde Kullanım
Birden fazla repo’yu yöneten bir organizasyondasın. Her repo için ayrı ayrı runner kurmak yerine organizasyon düzeyinde runner grupları tanımlayabilirsin.
Organizasyon ayarlarından (Settings > Actions > Runner groups) gruplar oluşturabilir, hangi repo’ların hangi runner grubuna erişebileceğini belirleyebilirsin. Bu yapı özellikle şu durumlar için değerli:
- Production runner grubu: Sadece main branch’ten tetiklenen workflow’lara açık, kısıtlı erişim
- Staging runner grubu: Geliştiricilerin serbestçe kullanabildiği, daha düşük kaynaklı makineler
- GPU runner grubu: Sadece ML ekibinin repo’larına açık, özel donanımlı sunucular
Organizasyon düzeyinde runner kurarken config komutunu biraz farklı kullanıyorsun:
# Organizasyon düzeyinde runner yapılandırması
./config.sh --url https://github.com/ORGANIZASYON_ADI
--token ORGANIZASYON_TOKEN
--name "org-runner-prod-01"
--labels "self-hosted,linux,production"
--runnergroup "production-runners"
Güvenlik: En Kritik Konu
Self-hosted runner kurduğunda güvenlik konusunu ciddiye almak zorundasın. GitHub hosted runner’lardan farklı olarak bu makine senin sorumluluğunda ve workflow her tetiklendiğinde bu sunucuda kod çalışıyor.
Fork repository riski: Public repo’larda self-hosted runner kullanıyorsan çok dikkatli ol. Herhangi biri fork açıp pull request gönderebilir ve eğer workflow fork’lardan tetikleniyorsa, gönderilen kod senin sunucunda çalışır.
# Fork PR'larından gelen workflow'ları kısıtla
on:
pull_request_target:
branches: [main]
jobs:
deploy:
# Sadece organizasyon üyelerinin PR'larında çalış
if: github.event.pull_request.head.repo.full_name == github.repository
runs-on: [self-hosted, linux]
Secrets yönetimi: Runner sunucusuna koymaman gereken şeyler:
- Hardcoded şifreler veya API key’ler
- Runner process’inin okuyabileceği kritik dosyalar
- Üretim veritabanına direkt bağlantı bilgileri (bunu GitHub Secrets üzerinden ver)
Firewall kuralları: Runner sunucunun dışarıya erişimini sınırla. Sadece gerekli servislere izin ver:
# UFW ile temel kural seti
sudo ufw default deny outgoing
sudo ufw default deny incoming
# SSH erişimine izin ver
sudo ufw allow 22/tcp
# GitHub API ve runner bağlantısı için
sudo ufw allow out 443/tcp
sudo ufw allow out 80/tcp
# İç ağ servislerine erişim (örnek: veritabanı)
sudo ufw allow out to 192.168.1.100 port 5432
sudo ufw enable
Runner’ı düzenli güncelle: Runner yazılımı kendini otomatik güncelleyebilir ama bunu takip etmek iyi pratik:
# Runner versiyonunu kontrol et
/opt/github-runner/run.sh --version
# Güncelleme için servisi durdur, yeni versiyonu indir, yeniden yapılandır
sudo systemctl stop actions.runner.*
# Yeni versiyonu indir ve kurulum adımlarını tekrarla
Paralel Runner Kurulumu ve Ölçeklendirme
Tek bir sunucuda birden fazla runner çalıştırabilirsin. Bu özellikle paralel job’lar için faydalı. Her runner farklı bir dizinde çalışmalı:
# İkinci runner için dizin oluştur
sudo mkdir -p /opt/github-runner-02
sudo chown github-runner:github-runner /opt/github-runner-02
# github-runner kullanıcısına geç
sudo su - github-runner
cd /opt/github-runner-02
# Paketi tekrar indir veya kopyala
cp -r /opt/github-runner/* /opt/github-runner-02/
cd /opt/github-runner-02
# Farklı isimle yapılandır
./config.sh --url https://github.com/KULLANICI/REPO
--token YENI_TOKEN
--name "production-runner-02"
--labels "self-hosted,linux,production"
--work /opt/github-runner-02/_work
# Servisi kur ve başlat
sudo /opt/github-runner-02/svc.sh install github-runner
sudo systemctl start actions.runner.KULLANICI-REPO.production-runner-02
Yüksek yük senaryolarında bunu otomatize etmek için Terraform veya Ansible kullanabilirsin. Özellikle AWS, GCP ya da Azure üzerinde spot/preemptible instance’larla dinamik runner ölçeklendirmesi yapmak mümkün, ama bu konu ayrı bir yazıyı hak ediyor.
Runner Bakımı ve İzleme
Runner’ın sağlıklı çalışıp çalışmadığını izlemek için birkaç pratik yöntem:
# Runner log dosyasının konumu
ls /opt/github-runner/_diag/
# En son log dosyasını izle
tail -f /opt/github-runner/_diag/Runner_*.log
# Disk kullanımını kontrol et (_work dizini şişebilir)
du -sh /opt/github-runner/_work/
# Work dizinini periyodik temizlik için cron job
# /etc/cron.d/cleanup-runner
0 3 * * * github-runner find /opt/github-runner/_work -maxdepth 2
-name "*.log" -mtime +7 -delete
Build artifact’larının birikmesi ciddi disk problemi yaratabilir. Özellikle Docker image build ediyorsan:
# Docker temizliği için cron job ekle
# Haftada bir eski image ve container'ları temizle
0 2 * * 0 github-runner docker system prune -af --filter "until=168h"
Runner’ın GitHub ile bağlantısını Prometheus + Grafana ile izlemek isteyenler için github-runner-exporter gibi üçüncü parti araçlara bakabilir. Ama basit bir kontrol için şu script işe yarıyor:
#!/bin/bash
# /usr/local/bin/check-runner.sh
RUNNER_SERVICE="actions.runner.KULLANICI-REPO.production-runner-01"
STATUS=$(systemctl is-active $RUNNER_SERVICE)
if [ "$STATUS" != "active" ]; then
echo "Runner DOWN! Yeniden başlatılıyor..."
systemctl restart $RUNNER_SERVICE
# Burada Slack veya e-posta bildirimi ekleyebilirsin
curl -X POST -H 'Content-type: application/json'
--data '{"text":"Runner yeniden başlatıldı!"}'
$SLACK_WEBHOOK_URL
fi
Bu script’i her 5 dakikada bir cron ile çalıştır:
*/5 * * * * root /usr/local/bin/check-runner.sh
Docker ile İzole Çalışma Ortamı
Her job’ın temiz bir ortamda çalışmasını istiyorsan container action’larını kullanabilirsin. Runner Docker kurulu olmalı:
# Docker kurulumu
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker github-runner
Workflow’da Docker kullanımı:
jobs:
build:
runs-on: [self-hosted, linux]
container:
image: node:20-alpine
options: --user root
steps:
- uses: actions/checkout@v4
- name: Build
run: npm ci && npm run build
Container kullanmak, runner’ın dosya sistemi kirlenmeden her build’in temiz ortamda çalışmasını sağlar. Bu özellikle birden fazla proje için aynı runner’ı kullandığında çok değerli.
Sık Karşılaşılan Sorunlar
Runner “Offline” görünüyor: Servis çalışıyor mu kontrol et, GitHub API’ye erişim var mı diye firewall kurallarını gözden geçir.
Permission denied hataları: Runner kullanıcısının gerekli dizinlere erişimi var mı? Deployment dizininin sahipliğini kontrol et.
Job stuck/askıda kalıyor: Workflow içinde interaktif komut bekleyen bir şey varsa askıda kalabilir. Tüm komutların non-interactive çalıştığından emin ol.
Disk doldu: _work dizini temizliği yaptın mı? Docker kullanıyorsan docker system prune gerekebilir.
# Hızlı tanı için
sudo su - github-runner
cd /opt/github-runner
./run.sh --check
Sonuç
Self-hosted runner kurmak başlangıçta biraz zahmetli görünebilir ama sağladığı esneklik buna değiyor. Özel ağ gereksinimleri, maliyet optimizasyonu veya özelleştirilmiş donanım ihtiyaçları olan ekipler için bu yaklaşım neredeyse zorunlu hale geliyor.
En kritik nokta güvenlik. Public repo kullanıyorsan özellikle dikkatli ol, runner’ı ayrı bir kullanıcıyla çalıştır, firewall kurallarını sıkı tut ve Secrets yönetimini GitHub’ın kendi mekanizmasına bırak. Bakım tarafında ise disk temizliği ve runner güncellemelerini göz ardı etme, bu iki konu zamanla ciddi baş ağrısı yaratıyor.
Sistemin oturtulduktan sonra “bu neden çalışmıyor?” diye saatler harcamak yerine CI/CD pipeline’larını iyileştirmeye vakit ayırabiliyorsunuz. Ve açıkçası, kendi sunucunda dönen bir pipeline’ın yeşil tik vermesi hosted runner’dan biraz daha tatmin edici geliyor.
