Ubuntu 24.04 VPS'e GitHub Actions Runner Kurulumu
Ubuntu 24.04 üzerinde self-hosted GitHub Actions runner kurulumu: dedicated user, checksum, config.sh ve systemd service. Fork pull request güvenlik riskini öğrenin.
Self-hosted GitHub Actions runner ne yapar
Self-hosted GitHub Actions runner, kendi VPS'nize kurduğunuz ve GitHub'dan işler isteyerek bunları donanımınızda çalıştıran bir programdır. Runner bir repository'ye kaydedilir, systemd service olarak kurulur ve her yeniden başlatmadan sonra otomatik olarak çalışır. İşi GitHub zamanlar. İşlemleri sunucunuz gerçekleştirir.
Sahip olduğunuz bir sunucuda CI (continuous integration) kullanmanın iki önemli nedeni vardır. Derleme dakikaları ücretlendirilmez. Ayrıca bir iş, yalnızca makinenizde bulunan warm build cache veya private network gibi kaynaklara erişebilir. Bunun karşılığında güvenlik riski oluşur. Runner, workflow file içinde belirtilen her şeyi, kendisine verdiğiniz kullanıcı olarak çalıştırır. Bu nedenle bir workflow file, tasarımı gereği remote code execution anlamına gelir. Private repository için bu durum genellikle kabul edilebilir; çünkü yalnızca güvendiğiniz kişiler workflow file ekleyebilir. Public repository için ise bu gerçek bir risktir. Fork pull request bölümünde mekanizma açıklanmaktadır.
Aşağıdaki işlemler Ubuntu 24.04 ve runner version 2.336.0 kullanılarak açıklanmıştır. Bu sürüm, July 2026 itibarıyla current release sürümüdür.
Başlamadan önce gerekenler
Yeni bir VPS üzerinde ilk on dakikada ulaşılan durumda, normal bir admin hesabına ve sudo yetkisine sahip bir VPS ile başlayın. Gelen bağlantılar için bir port açmanız gerekmez. Runner, GitHub'a giden bir HTTPS (hypertext transfer protocol secure) bağlantısı açar ve iş beklerken bu bağlantıyı açık tutar. Bu nedenle GitHub sunucunuza hiçbir zaman bağlanmaz. Güvenlik duvarınız dış dünyaya kapalı kalabilir ve işler yine de ulaşır.
Ayrıca depo üzerinde admin yetkilerine sahip olmanız gerekir. Kayıt belirteci depo ayarlarında gösterilir.
Runner için özel bir kullanıcı oluşturma
Runner'ı hiçbir zaman root veya kendi yönetici kullanıcınız olarak çalıştırmayın. Her iş, runner kullanıcısının yetkilerini devralır. Bu nedenle runner kullanıcısı sudo kullanabiliyorsa sudo çağıran bir iş akışı başarılı olur. Yalnızca kendi home dizinine sahip, başka hiçbir kaynağın sahibi olmayan ayrıcalıksız bir kullanıcı oluşturun. VPS üzerinde en az ayrıcalıklı kullanıcı hesapları, genel yaklaşımı açıklar. Aşağıda bu kullanım için özel yapılandırma yer alır.
sudo useradd -m -s /bin/bash gharunner
sudo passwd -l gharunner
sudo chmod 750 /home/gharunner
sudo install -d -m 700 -o gharunner -g gharunner /home/gharunner/actions-runnerpasswd -l parolayı kilitler. Böylece hiç kimse gharunner olarak bu parolayla oturum açamaz. Runner dizininde 700 modunun kullanılması önemlidir. Runner kimlik bilgilerini burada düz metin olarak saklar ve checkout özel kaynak kodu içerebilir.
Devam etmeden önce her iki özelliği de kontrol edin:
sudo passwd -S gharunner
sudo -l -U gharunnerpasswd -S, gharunner L ile başlayan bir satır yazdırır. Bu satırda L, parolanın kilitli olduğu anlamına gelir. sudo -l -U gharunner yanıtı is not allowed to run sudo olmalıdır. Bunun yerine izin verilen komutların listesini yazdırırsa hesap bir sudo grubundadır ve az önce oluşturulan yalıtım ortadan kalkmıştır.
Runner'ı indirme ve tar arşivini doğrulama
Bundan sonra runner kullanıcısı olarak çalışın.
sudo -iu gharunner
cd ~/actions-runner
RUNNER_VERSION=2.336.0
curl -fL -o actions-runner-linux-x64-${RUNNER_VERSION}.tar.gz \
"https://github.com/actions/runner/releases/download/v${RUNNER_VERSION}/actions-runner-linux-x64-${RUNNER_VERSION}.tar.gz"Mimariden emin değilseniz önce uname -m komutunu çalıştırın. x86_64, yukarıdaki linux-x64 dosyasını alır. aarch64 ise actions-runner-linux-arm64-${RUNNER_VERSION}.tar.gz dosyasını alır.
Şimdi indirdiğiniz dosyayı doğrulayın. Aşağıdaki SHA256 (secure hash algorithm, 256 bit) değeri, 2.336.0 x64 tar arşivine aittir. GitHub, geçerli sürümün değerini release sayfasında ve New self-hosted runner ekranında gösterir. Değer her sürümde değişir. Farklı bir sürüm kurarken değeri bu sayfalardan kopyalayın.
echo "04cf0be1aff4c3ec3554466c39124ca250e3effd8873bb7e8d68535aa9505d5d actions-runner-linux-x64-2.336.0.tar.gz" | sha256sum -cİndirme başarılıysa tek satır görüntülenir:
actions-runner-linux-x64-2.336.0.tar.gz: OKKesilmiş veya değiştirilmiş bir dosyada hata ve uyarı görüntülenir:
actions-runner-linux-x64-2.336.0.tar.gz: FAILED
sha256sum: WARNING: 1 computed checksum did NOT matchDoğrulamayı atlamayın ve sorunu tar komutunun bulmasını beklemeyin. Eksik yazılmış bir arşiv gzip: stdin: unexpected end of file ve tar: Unexpected EOF in archive ile başarısız olur. Bu, dosyanın bozuk olduğunu gösterir. Ancak dosyanın eksik mi yazıldığını yoksa değiştirilip değiştirilmediğini göstermez.
tar xzf ./actions-runner-linux-x64-2.336.0.tar.gz
lstarball'ın içeriği ve içermedikleri
Ayıklama sonrasında dizinde config.sh, run.sh, env.sh, safe_sleep.sh, bin/ ve externals/ bulunur. bin/, runner ikili dosyalarını ve bin/installdependencies.sh dosyasını içerir. externals/, JavaScript action'larının çalıştırıldığı birlikte gelen Node runtime'ını içerir.
Henüz svc.sh bulunmaz. GitHub belgelerinde bu dosya, "runner başarıyla eklendikten sonra oluşturulan" script olarak açıklanır. Bunun nedeni, dosyanın repository ve runner adınız service adına işlenmiş bir şablondan oluşturulmasıdır. Bu nedenle ./config.sh öncesinde sudo ./svc.sh install çalıştırılırsa sudo: ./svc.sh: command not found hatası oluşur. Önce kayıt işlemini tamamlayın, ardından service'i yükleyin.
Runner bağımlılıklarını kurma
Runner bir .NET uygulamasıdır; bu nedenle birkaç paylaşılan kitaplığa ihtiyaç duyar. Runner kullanıcısının kabuğundan çıkmayın ve bunları sudo ile kurun; çünkü script sistem paket veritabanına yazmaktadır.
exit
cd /home/gharunner/actions-runner
sudo ./bin/installdependencies.shUbuntu 24.04 üzerinde bu işlem libkrb5-3, zlib1g, liblttng-ust1t64, libssl3t64 ve libicu74 paketlerini kurar. Script her kitaplık için birkaç sürüm adı dener ve dağıtımınızda bulunan sürümü kullanır. Bu nedenle aynı script eski Ubuntu sürümlerinde ve Debian üzerinde de çalışır.
Bu adımı atlarsanız ./config.sh herhangi bir işlem yapmadan önce durur:
Dependencies is missing for Dotnet Core 6.0
Execute sudo ./bin/installdependencies.sh to install any missing Dotnet Core 6.0 dependencies.Eksik libicu, farklı bir ilk satır olan Libicu's dependencies is missing for Dotnet Core 6.0 ile aynı öneriyi verir. Her ikisinin nedeni aynıdır: config.sh başlamadan önce paketlenmiş kitaplıklara karşı ldd komutunu çalıştırır. Bu nedenle çözümlenemeyen bir bağlantı, script'in daha sonra anlaşılması güç bir çökme üretmesi yerine durmasına neden olur.
Çalıştırıcıyı deponuza kaydedin
Depodan bir belirteç alın. Settings, ardından Actions, sonra Runners ve New self-hosted runner seçeneklerini açın. Sayfada A ile başlayan bir kayıt belirteci görüntülenir. Bu belirtecin süresi oluşturulduktan bir saat sonra dolar. Bu nedenle belirteci yapıştırmaya hazır olduğunuzda oluşturun.
Kaydı runner kullanıcısı olarak yapın. config.sh sudo altında çalışmayı reddeder.
sudo -iu gharunner
cd ~/actions-runner
./config.sh --url https://github.com/YOUR-USER/YOUR-REPO \
--token PASTE_REGISTRATION_TOKEN_HERE \
--name vps-runner-1 \
--labels vps \
--work _work \
--unattended \
--replaceBu seçeneklerin işlevleri. --name, runner'ın depoda nasıl görüneceğini belirler. Bu nedenle altı ay sonra da tanıyabileceğiniz bir ad seçin. --labels kendi etiketlerinizi ekler. Runner, ayrıca herhangi bir işlem yapılmadan self-hosted, Linux ve X64 etiketlerini taşır. --work, checkout işlemlerinin runner dizini içinde yerleşeceği dizinin adını belirler. --unattended etkileşimli istemleri varsayılan değerleriyle yanıtlar. Komut bir betikte çalıştırıldığında istenen davranış budur. --replace, aynı ada sahip mevcut bir kaydın üzerine yazar ve komutun başarısız olmasını engeller. Sunucuyu yeniden oluşturduğunuzda istenen davranış budur.
Başarılı bir çalıştırma şu satırlarla sona erer:
√ Runner successfully added
√ Runner connection is good
√ Settings Saved.Kayıt artık runner dizininde .runner, .credentials ve .credentials_rsaparams olarak bulunur. Son iki değer bu runner'ı GitHub'a tanımlar. Bu nedenle bu değerleri okuyabilen herkes runner'ı taklit edebilir. Dizin izinlerinin 700 olmasının ve kullanıcının sudo yetkisinin bulunmamasının nedeni budur.
Runner'ı systemd hizmeti olarak kurma
Bir terminalde ./run.sh komutunu çalıştırmak tek bir test için yeterlidir, ancak SSH oturumunuz sona erdiğinde işlem de sona erer. Runner'ın sistem açılışında başlaması için hizmeti kurun. VPS üzerinde systemd hizmetleri ve zamanlayıcıları bağlantısında birim dosyalarının kendisi açıklanmaktadır. Burada svc.sh birim dosyasını sizin için oluşturur.
exit
cd /home/gharunner/actions-runner
sudo ./svc.sh install gharunner
sudo ./svc.sh start
sudo ./svc.sh statussvc.sh, /etc/systemd/system dizinine birim dosyası yazdığı ve hizmeti etkinleştirdiği için root yetkisi gerektirir. install sonrasındaki bağımsız değişken, hizmetin çalışacağı kullanıcıdır. gharunner değerini açıkça belirtin. Bağımsız değişken verilmezse betik $SUDO_USER değerini kullanır. Bu, yönetici hesabınızdır. Bu durumda her iş sudo kullanabilen bir kullanıcı olarak çalışır.
Birim, depo ve runner adlarından türetilen actions.runner.YOUR-USER-YOUR-REPO.vps-runner-1.service biçiminde adlandırılır. Bu adı elle yazmanız gerekmez:
systemctl list-units 'actions.runner.*'
sudo journalctl -u 'actions.runner.*' -n 20 --no-pagerSağlıklı bir runner, √ Connected to GitHub ifadesini ve ardından Listening for Jobs ile biten bir satırı günlüğe yazar. Deponun Runners sayfasında runner, Idle olarak gösterilir. Offline olarak gösterilen bir runner ya çalışmıyordur ya da GitHub'a 443 numaralı port üzerinden ulaşamıyordur.
Runner'a iş gönderme
runs-on, runner'ı etikete göre seçer. self-hosted ile birlikte kendi etiketinizi belirtin; böylece iş, amaçlanmayan bir runner'a atanamaz.
name: build
on:
push:
branches: [main]
jobs:
build:
runs-on: [self-hosted, linux, vps]
steps:
- uses: actions/checkout@v5
- run: uname -aİş Waiting for a runner to pick up this job durumunda bekliyorsa etiketler eşleşmiyordur. runs-on içindeki her etiket runner üzerinde mevcut olmalıdır. Bu nedenle fazladan tek bir sözcük bile işin herhangi bir hata vermeden kuyruğa alınmış olarak kalmasına neden olur. Listeyi, repository ayarlarında runner'ın yanında gösterilen etiketlerle karşılaştırın.
Kendi barındırdığınız runner'lar ile genel depolar neden bir arada kullanılmamalıdır
İnsanların atladığı bölüm budur. GitHub'ın yönergesi açıktır: Kendi barındırdığınız runner'lar "genel depolar için neredeyse hiçbir zaman kullanılmamalıdır" ve bu runner'lar "geçici, temiz sanal makinelerde çalıştırılma garantisine sahip değildir; bir workflow içindeki güvenilmeyen kod tarafından kalıcı olarak ele geçirilebilir".
Mekanizma basittir. Bir fork'tan gelen pull request, workflow dosyasının kendi kopyasını içerir. Genel deponuz pull request workflow'larını runner'ınızda çalıştırıyorsa, depoyu fork'layabilen herkes VPS'nizde kendi komutlarını çalıştıran bir workflow önerebilir. Yazma erişimine ihtiyaçları yoktur, çünkü önerdikleri şey doğrudan çalıştırılan şeydir.
Onay ayarları bu riski azaltır, ancak sorunu çözmez. Genel bir deponun varsayılan ilkesi, ilk kez katkıda bulunan bir kişinin fork workflow'u için bir maintainer'ın onay vermesini ister. Bu kişiyi bir kez onayladıktan sonra, sonraki pull request'leri yeni bir uyarı olmadan çalışır. Dolayısıyla güvenlik kapısı, her seferinde bir insanın diff'i okumasına dayanır; bir build script'inin üç seviye altına gizlenmiş bir payload kolayca gözden kaçabilir.
Bir fork pull request'i sizin secrets değerlerinizi almaz ve GITHUB_TOKEN salt okunurdur. Bu, GitHub içindeki zararı sınırlar. Sunucunuz açısından hiçbir şey değiştirmez. Saldırganın gharunner olarak bir shell erişimi vardır. Bu nedenle bu kullanıcının okuyabildiği her dosyayı okuyabilir, VPS'in özel ağında erişebildiği her yere ulaşabilir ve ~/.bashrc içine veya sonraki job sırasında çalışacak bir kullanıcı systemd unit'ine kalıcı bir içerik bırakabilir.
--ephemeral ile kayıt yapılması, runner'ın tek bir job kabul edip ardından kaydını kaldırmasını sağlar. Böylece bir job sonraki job'ın workspace'ini okuyamaz. Bu yalnızca her job için makineyi veya container'ı yeniden oluşturan bir işlem varsa yardımcı olur. Çünkü runner kullanıcısının home directory'sine yazılan bir backdoor, yeniden kayıt işleminden sonra da varlığını sürdürür.
İzlenecek kurallar kısadır. Kendi barındırdığınız runner'ları özel depolar için kullanın. Bir runner'ı genel bir depoya bağlamanız zorunluysa, fork pull request'lerini bu runner üzerinde çalıştırmayın, sunucuda başka hiçbir şey barındırmayın ve makineyi kullanımdan kaldırılabilir olarak değerlendirin.
Docker işleri ve fiilen root olan grup
Konteyner işleri, hizmet konteynerleri ve docker build çağıran tüm iş akışı adımları, runner ana bilgisayarında bir Docker daemon gerektirir. Docker'ı normal yöntemle kurun. Bu yöntem VPS üzerinde Docker ve Docker Compose bölümünde açıklanır. Ardından runner kullanıcısını docker grubuna ekleyin.
Bunu yapmadan önce sonucu değerlendirin. docker grubuna üyelik root yetkisine eşdeğerdir. Bunun nedeni, bir konteynerin / yolunu bind mount olarak bağlayıp içinde root olarak çalışabilmesidir. Bu nedenle Docker socket'i ile iletişim kurabilen bir iş akışı, /etc/shadow dahil olmak üzere VPS üzerindeki tüm dosyaları okuyabilir ve yazabilir. Güvenilen katkıcılara sahip özel bir depoda bu risk kabul edilebilir. Diğer durumlarda bu yetki, ayrıcalıksız kullanıcı kullanmanın amacını ortadan kaldırır. Rootless Docker, konteyner derlemelerini runner kullanıcısının kendi yetkileriyle sınırlar. Bunun karşılığında daha yavaş bir depolama sürücüsü kullanılır ve ayrıcalıklı konteynerler çalıştırılamaz.
Güncelleme ve runner'ın düzgün şekilde kaldırılması
Self-hosted runner varsayılan olarak kendini günceller. Yeni bir sürümü algılar, kendi dosyalarını değiştirir ve servisi yeniden başlatır. Bu nedenle normalde herhangi bir işlem yapmanız gerekmez. ./config.sh --disableupdate, sabit bir sürüm gerektiğinde otomatik güncellemeyi devre dışı bırakır. Bundan sonra güncelleme sizin sorumluluğunuzdadır. GitHub belgelerinde, --disableupdate ile yapılandırılmış bir runner'ın elle güncellenmesi gerektiği açıkça belirtilir.
Elle yapılan güncelleme kaydı korur. Bunun nedeni .runner ve .credentials öğelerinin tarball içinde bulunmamasıdır. Servisi durdurun, yeni tarball'ı gharunner olarak indirin ve sağlama toplamını doğrulayın. Tarball'ı tar xzf ile aynı dizinin üzerine çıkarın. Ardından servisi yeniden başlatın:
cd /home/gharunner/actions-runner
sudo ./svc.sh stop
sudo ./svc.sh startRunner'ı kaldırmak için önce servisi kaldırın, ardından kaydını silin. Kaldırma belirteci, aynı Runners sayfasında runner'ın kendi Remove düğmesinin altında bulunur.
cd /home/gharunner/actions-runner
sudo ./svc.sh stop
sudo ./svc.sh uninstall
sudo -iu gharunner
cd ~/actions-runner
./config.sh remove --token PASTE_REMOVAL_TOKEN_HERERunner'ın kaydını silmeden dizini silmek, runner'ın depoda Offline olarak listelenmesine neden olur. GitHub, runner kendisinin kaldırıldığını bildirdiğinde veya bir yönetici girdiyi elle sildiğinde runner'ın artık mevcut olmadığını öğrenir.
Göreceğiniz dizelerle birlikte hata durumları
Must not run with sudo. config.sh root olarak çalıştırıldığında bu iletiyi yazdırır ve çıkar. Bu denetim kasıtlıdır; çünkü _work altındaki root sahipli dosyalar, hizmet kullanıcısı olarak çalışan sonraki tüm işleri bozar. ./config.sh komutunu gharunner olarak çalıştırın. RUNNER_ALLOW_RUNASROOT değişkeni bu denetimi geçersiz kılar; ancak bu değişkenin kullanılması yalnızca sorunun daha sonra ortaya çıkmasına neden olur.
sudo: ./svc.sh: command not found. Doğru dizindesiniz. svc.sh henüz mevcut değildir; çünkü config.sh henüz bir kayıt işlemini tamamlamamıştır. Runner'ı kaydedin, ardından hizmeti yükleyin.
Http response code: NotFound from 'POST https://api.github.com/actions/runner-registration'. Belirteç geçerli bir kayıt belirteci değildir. Belirtecin süresi dolmuş olabilir; kayıt belirteçleri yalnızca 1 saat geçerlidir. Alternatif olarak, Runners sayfasındaki kayıt belirteci yerine bir personal access token yapıştırılmış olabilir. Yeni bir belirteç oluşturun ve yeniden yapıştırın.
Dependencies is missing for Dotnet Core 6.0. sudo ./bin/installdependencies.sh komutunu runner dizininden root olarak çalıştırın, ardından yeniden kaydedin.
Yeniden başlatma sonrasında Runner Offline. systemctl is-enabled 'actions.runner.*' komutunu çalıştırın. Hiçbir öğe listelenmiyorsa ./svc.sh install hiç çalıştırılmamıştır; bu nedenle runner yalnızca terminal oturumunuz içinde var olmuştur. Birim etkinse ve runner hâlâ Offline durumundaysa journalctl -u 'actions.runner.*' dosyasını okuyun ve giden HTTPS bağlantısını denetleyin.
Disk dolar. Checkout'lar, derleme önbellekleri ve Docker görüntüleri _work altında ve runner kullanıcısının home dizininde birikir; bunları sizin için temizleyen bir işlem yoktur. du -sh /home/gharunner/actions-runner/_work dosyasını izleyin ve disk dolmadan önce zamanlanmış bir temizlik işlemi ekleyin.
FAQ
sudo ./svc.sh install neden command not found diyor?
Bunun nedeni svc.sh dosyasının runner tarball'ında bulunmamasıdır. Bu dosya, ./config.sh kaydı tamamlandığında runner dizininde oluşturulur. Hizmet adı, repository ve runner adınız kullanılarak oluşturulur. Önce ./config.sh komutunu runner kullanıcısı olarak çalıştırın. Bundan sonra sudo ./svc.sh install gharunner betiği bulur ve actions.runner.OWNER-REPO.RUNNER-NAME.service adlı bir unit dosyasını /etc/systemd/system içine yazar.
Self-hosted runner için firewall portu açılması gerekir mi?
Hayır. Runner, GitHub'a giden bir HTTPS bağlantısı açar ve işleri beklerken bu bağlantıyı açık tutar. Bu nedenle GitHub, VPS'nize hiçbir zaman bağlantı başlatmaz. Giden 443 trafiğine izin verin ve gelen bağlantı kurallarını kapalı bırakın. Hizmeti çalışırken runner Offline görünüyorsa gelen kurallar yerine giden filtrelemeyi ve DNS'i kontrol edin.
Public repository üzerinde self-hosted runner kullanılabilir mi?
Kullanılabilir, ancak GitHub bunu önermez. Bir fork'tan gelen pull request, kendi workflow dosyasını içerir. Bu nedenle repository'nizi fork edebilen herkes makinenizde çalışacak komutlar önerebilir. Onay istemi yalnızca bir katkıcının ilk çalıştırmasını kapsar. Bir runner'ı public repository'ye bağlarsanız fork pull request workflow'larını devre dışı bırakın, bu sunucuda başka hiçbir şey barındırmayın ve makineyi belirli aralıklarla yeniden oluşturun.
Kayıt Http response code: NotFound ile neden başarısız oluyor?
Kayıt çağrısı, yalnızca URL yanlış olduğunda değil, kimlik bilgisi yanlış olduğunda da NotFound yanıtı verir. Bu durum hata mesajını yanıltıcı hâle getirir. Registration token'ları gösterildikten bir saat sonra geçerliliğini yitirir. Personal access token bu çağrı için kabul edilmez. Settings, Actions, Runners, New self-hosted runner bölümünü yeniden açın, yeni token'ı kopyalayın ve --url değerinin yönetici haklarına sahip olduğunuz bir repository'yi gösterdiğini doğrulayın.