SSD Nodes Learn Hosting plans →
Rehberler Matt ConnorYazan Matt Connor · Güncellendi 2026-08-07

VPS üzerinde self-hosted GitHub Actions runner kurulumu

Ubuntu 24.04 sunucuda GitHub Actions runner kurulumu yapın. Dedicated kullanıcı oluşturma, checksum doğrulaması, config.sh yapılandırması ve systemd servisi adımlarını öğrenin.

Verified Every command ran end-to-end on a fresh Ubuntu 24.04 server, July 30, 2026.

Self-hosted GitHub Actions runner nedir

Self-hosted GitHub Actions runner, kendi VPS'nize kurduğunuz ve GitHub'dan iş talep ederek bu işleri donanımınız üzerinde çalıştıran bir programdır. Runner'ı belirli bir depoya kaydedip systemd servisi olarak yapılandırdığınızda, her yeniden başlatma sonrasında otomatik olarak çalışmaya devam eder. GitHub işi planlar, sunucunuz ise işi gerçekleştirir.

Kendi sunucunuz üzerinde CI (sürekli entegrasyon) çalıştırmanın iki temel avantajı vardır. Derleme süreleri (build minutes) kotaya dahil edilmez ve işler; hazır bir derleme önbelleği veya özel bir ağ gibi yalnızca makinenizin erişebildiği kaynaklara ulaşabilir. Bunun bedeli ise güvenliktir. Runner, kendisine tanımladığınız kullanıcı yetkileriyle iş akışı dosyasında (workflow file) belirtilen her komutu çalıştırır; bu nedenle iş akışı dosyaları tasarım gereği uzaktan kod yürütme (remote code execution) işlevi görür. Özel (private) depolarda bu durum güvenlidir çünkü yalnızca güvendiğiniz kişiler dosya ekleyebilir. Halka açık (public) depolarda ise bu ciddi bir risk oluşturur; fork pull request'leri hakkındaki bölüm bu mekanizmayı açıklamaktadır.

Aşağıdaki tüm işlemler, Temmuz 2026 itibarıyla güncel sürüm olan 2.336.0 runner sürümü ve Ubuntu 24.04 işletim sistemi temel alınarak hazırlanmıştır.

Başlamadan önce gerekenler

Yeni bir VPS üzerinde ilk on dakika rehberinde ulaştığınız, sudo yetkilerine sahip standart bir yönetici hesabıyla yapılandırılmış bir VPS ile başlayın. Gelen bağlantılar için herhangi bir port açmanız gerekmez. Runner, GitHub'a giden bir HTTPS (hypertext transfer protocol secure) bağlantısı başlatır ve iş beklerken bu bağlantıyı açık tutar; bu nedenle GitHub sunucunuza doğrudan bağlanmaz. Güvenlik duvarınız dış dünyaya kapalı kalsa dahi işler sunucunuza ulaşmaya devam eder.

Ayrıca, kayıt belirteci (registration token) depo ayarlarında görüntülendiği için depo üzerinde yönetici haklarına sahip olmanız gerekir.

Runner için özel bir kullanıcı oluşturun

Runner'ı asla root veya kendi yönetici kullanıcınızla çalıştırmayın. Her iş, runner kullanıcısının haklarını devralır; bu nedenle sudo komutunu çağıran bir iş akışı, runner kullanıcısı sudo kullanabiliyorsa başarılı olur. Yalnızca kendi ev dizini dışında hiçbir şeyin sahibi olmayan, ayrıcalıksız bir kullanıcı oluşturun. VPS üzerinde en az ayrıcalıklı kullanıcı hesapları genel modeli kapsar. Burada ise özel durum ele alınmıştı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-runner

passwd -l parolayı kilitler, böylece hiç kimse parola ile gharunner kullanıcısı olarak giriş yapamaz. Runner dizinindeki 700 modu önemlidir çünkü runner kimlik bilgilerini orada düz metin olarak saklar ve bir checkout işlemi özel kaynak kodları içerebilir.

Devam etmeden önce her iki özelliği de kontrol edin:

sudo passwd -S gharunner
sudo -l -U gharunner

passwd -S, gharunner L ile başlayan bir satır yazdırır; burada L parolanın kilitli olduğu anlamına gelir. sudo -l -U gharunner, is not allowed to run sudo ile yanıt vermelidir. Eğer bunun yerine izin verilen komutların bir listesini yazdırırsa, hesap bir sudo grubundadır ve oluşturduğunuz izolasyon geçersiz kalmış demektir.

Runner'ı indirin ve tarball dosyasını kontrol edin

Bu noktadan itibaren runner kullanıcısı olarak işlem yapı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"

Mimari konusunda emin değilseniz önce uname -m komutunu çalıştırın. x86_64, yukarıdaki linux-x64 dosyasını kullanır. aarch64 ise actions-runner-linux-arm64-${RUNNER_VERSION}.tar.gz dosyasını işler.

Şimdi indirdiğiniz dosyayı doğrulayın. Aşağıdaki SHA256 (secure hash algorithm, 256 bit) değeri 2.336.0 x64 tarball dosyası içindir. GitHub, güncel sürümün değerini sürüm sayfasında ve New self-hosted runner ekranında yayınlar. Bu değer her sürümde değiştiği için, farklı bir sürüm kurarken değeri ilgili sayfadan kopyalayın.

echo "04cf0be1aff4c3ec3554466c39124ca250e3effd8873bb7e8d68535aa9505d5d  actions-runner-linux-x64-2.336.0.tar.gz" | sha256sum -c

Başarılı bir indirme işlemi tek satırlık bir çıktı üretir:

actions-runner-linux-x64-2.336.0.tar.gz: OK

Eksik veya değiştirilmiş bir dosya, hata mesajı ve uyarı döndürür:

actions-runner-linux-x64-2.336.0.tar.gz: FAILED
sha256sum: WARNING: 1 computed checksum did NOT match

Kontrol adımını atlayıp sorunu tar komutunun bulmasını beklemeyin. Yarım kalmış bir arşiv dosyası gzip: stdin: unexpected end of file ve tar: Unexpected EOF in archive hatalarıyla başarısız olur; bu hata size dosyanın bozuk olduğunu söyler ancak dosyanın eksik mi indiğini yoksa değiştirildiğini mi belirtmez.

tar xzf ./actions-runner-linux-x64-2.336.0.tar.gz
ls

Tarball içeriği ve kapsamı

Çıkarma işleminden sonra dizin config.sh, run.sh, env.sh, safe_sleep.sh, bin/ ve externals/ dosyalarını içerir. bin/, çalıştırıcı ikili dosyalarını ve bin/installdependencies.sh öğesini barındırır. externals/, JavaScript eylemlerinin üzerinde yürütüldüğü paketlenmiş Node çalışma zamanını tutar.

Henüz svc.sh mevcut değildir. GitHub belgeleri bunu, "çalıştırıcı başarıyla eklendikten sonra oluşturulan" betik olarak tanımlar; çünkü bu betik, depo ve çalıştırıcı adınızın servis adına işlendiği bir şablondan yazılır. Bu nedenle ./config.sh öncesinde sudo ./svc.sh install çalıştırmak sudo: ./svc.sh: command not found hatasıyla sonuçlanır. Önce kaydı tamamlayın, ardından servisi kurun.

Runner bağımlılıklarını yükleme

Runner bir .NET uygulamasıdır, bu nedenle bazı paylaşılan kütüphanelere ihtiyaç duyar. Runner kullanıcısının kabuğundan çıkın ve bunları sudo ile yükleyin; çünkü betik, sistem paket veritabanına yazma işlemi gerçekleştirir.

exit
cd /home/gharunner/actions-runner
sudo ./bin/installdependencies.sh

Ubuntu 24.04 üzerinde bu işlem libkrb5-3, zlib1g, liblttng-ust1t64, libssl3t64 ve libicu74 paketlerini çeker. Betik, her kütüphane için çeşitli sürüm adlarını dener ve dağıtımınızın sunduğu sürümü tutar; aynı betiğin hem eski Ubuntu sürümlerinde hem de Debian üzerinde çalışmasının nedeni budur.

Bu adımı atlarsanız ./config.sh herhangi bir işlem yapmadan durur:

Dependencies is missing for Dotnet Core 6.0
Execute sudo ./bin/installdependencies.sh to install any missing Dotnet Core 6.0 dependencies.

Eksik bir libicu, farklı bir ilk satır olan Libicu's dependencies is missing for Dotnet Core 6.0 altında aynı tavsiyeyi verir. Her ikisi de aynı yerden kaynaklanır: config.sh, başlatılmadan önce paketlenmiş kütüphanelere karşı ldd çalıştırır; böylece çözümlenemeyen bir bağlantı, daha sonra kafa karıştırıcı bir çökmeye yol açmak yerine betiği durdurur.

Runner'ı deponuz ile kaydedin

Depodan bir token alın. Settings, ardından Actions, ardından Runners ve son olarak New self-hosted runner yolunu izleyin. Sayfa, A ile başlayan bir kayıt token'ı gösterir. Bu token oluşturulduktan bir saat sonra geçerliliğini yitirir; bu nedenle yapıştırmaya hazır olduğunuzda oluşturun.

Runner kullanıcısı olarak kayıt işlemini gerçekleştirin. 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 \
  --replace

Bu bayrakların işlevleri şöyledir: --name, runner'ın depoda nasıl görüneceğini belirler; bu nedenle altı ay sonra da tanıyabileceğiniz bir isim seçin. --labels kendi etiketlerinizi eklemenizi sağlar; runner zaten herhangi bir ekleme yapmadan self-hosted, Linux ve X64 etiketlerini taşır. --work, checkout işlemlerinin runner dizini içinde nereye yapılacağını belirler. --unattended, etkileşimli istemleri varsayılan değerleriyle yanıtlar; komut bir betik içinde çalıştırıldığında istenen davranış budur. --replace, aynı isimli mevcut bir kaydı hata vermek yerine devralır; sunucuyu yeniden oluşturduğunuzda istenen davranış budur.

Başarılı bir çalıştırma şu satırlarla biter:

√ Runner successfully added
√ Runner connection is good
√ Settings Saved.

Kayıt bilgileri artık runner dizininde .runner, .credentials ve .credentials_rsaparams dosyalarında tutulur. Son iki dosya, bu runner'ı GitHub nezdinde tanımlar; bu nedenle bunları okuyabilen herkes runner'ın kimliğine bürünebilir. Dizinin 700 modunda olmasının ve kullanıcının sudo yetkisinin bulunmamasının nedeni budur.

Runner'ı systemd servisi olarak kurma

./run.sh komutunu terminalde çalıştırmak tek seferlik testler için uygundur ancak SSH oturumunuz kapandığında süreç sonlanır. Runner'ın sistem açılışında otomatik başlaması için servisi kurun. VPS üzerinde systemd servisleri ve zamanlayıcılar dokümanı, unit dosyalarının yapısını açıklar. Burada svc.sh sizin yerinize bir tane oluşturur.

exit
cd /home/gharunner/actions-runner
sudo ./svc.sh install gharunner
sudo ./svc.sh start
sudo ./svc.sh status

svc.sh, /etc/systemd/system dizinine bir unit dosyası yazıp bunu etkinleştirdiği için root yetkisi gerektirir. install bayrağından sonra gelen argüman, servisin hangi kullanıcı yetkisiyle çalışacağını belirler. gharunner değerini açıkça belirtin. Herhangi bir argüman girilmezse betik, yönetici hesabınız olan $SUDO_USER değerine döner; bu durumda tüm işler sudo yetkisine sahip bir kullanıcı olarak çalıştırılır.

Unit dosyası, depo ve runner ismine göre actions.runner.YOUR-USER-YOUR-REPO.vps-runner-1.service formatında adlandırılır. Bunu manuel yazmanıza gerek yoktur:

systemctl list-units 'actions.runner.*'
sudo journalctl -u 'actions.runner.*' -n 20 --no-pager

Sorunsuz çalışan bir runner, √ Connected to GitHub logunu ve ardından Listening for Jobs ile biten bir satırı çıktı olarak verir; deponun Runners sayfasında ise durumu Idle olarak görünür. Offline görünen bir runner ya çalışmıyordur ya da 443 numaralı port üzerinden GitHub'a erişemiyordur.

Runner'a iş gönderme

runs-on, bir runner'ı etiketine göre seçer. self-hosted değerini ve kendi etiketinizi belirtin; böylece bir iş, hedeflemediğiniz bir runner üzerinde çalışmaz.

name: build
on:
  push:
    branches: [main]
jobs:
  build:
    runs-on: [self-hosted, linux, vps]
    steps:
      - uses: actions/checkout@v5
      - run: uname -a

Eğer iş Waiting for a runner to pick up this job aşamasında bekliyorsa, etiketler eşleşmiyor demektir. runs-on içindeki her etiketin runner üzerinde tanımlı olması gerekir; fazladan bir kelime bile işin herhangi bir hata vermeden kuyrukta kalmasına neden olur. Listeyi, depo ayarlarında runner'ın yanında gösterilen etiketlerle karşılaştırın.

Neden self-hosted runner'lar ve herkese açık depolar birlikte kullanılmamalıdır

İnsanların atladığı kısım burasıdır. GitHub'ın rehberliği nettir: self-hosted runner'lar "herkese açık depolar için neredeyse hiçbir zaman kullanılmamalıdır" ve "geçici, temiz sanal makinelerde çalışma garantisine sahip değildir; iş akışındaki güvenilmeyen kodlar tarafından kalıcı olarak ele geçirilebilirler".

Mekanizma basittir. Bir fork üzerinden gelen pull request, kendi iş akışı dosyası kopyasını beraberinde getirir. Eğer herkese açık deponuz, pull request iş akışlarını runner'ınız üzerinde çalıştırıyorsa, depoyu fork edebilen herkes kendi komutlarını VPS'niz üzerinde çalıştıracak bir iş akışı önerebilir. Yazma erişimine ihtiyaçları yoktur, çünkü önerdikleri şey zaten çalışacak olan şeydir.

Onay ayarları bunu düzeltmeden hafifletir. Herkese açık bir depo için varsayılan politika, bir bakımcının ilk kez katkıda bulunan birinin fork iş akışını onaylamasını ister. Bir kişiyi bir kez onayladıktan sonra, sonraki pull request'leri yeni bir uyarı olmadan çalışır. Yani engel, her seferinde bir diff okuyan insandır ve bir build betiğinin üç seviye altına gizlenmiş bir payload'u gözden kaçırmak kolaydır.

Bir fork pull request'i sırlarınızı almaz ve GITHUB_TOKEN salt okunurdur. Bu, GitHub içindeki hasarı sınırlar. Sunucunuz için ise hiçbir şey yapmaz. Saldırgan gharunner olarak bir shell'e sahiptir; bu nedenle o kullanıcının okuyabildiği her dosyayı okuyabilir, VPS'nin özel ağında erişebildiği her yere ulaşabilir ve ~/.bashrc dizininde veya bir sonraki iş sırasında çalışan bir kullanıcı systemd biriminde bir şeyler bırakabilir.

--ephemeral ile kayıt yapmak, runner'ın bir işi kabul edip ardından kaydını silmesini sağlar; böylece bir iş, bir sonraki işin çalışma alanını okuyamaz. Bu yalnızca makineyi veya container'ı her iş için yeniden oluşturan bir yapı varsa işe yarar, çünkü runner kullanıcısının ev dizinine yazılan bir arka kapı, yeni bir kayıttan sonra da varlığını sürdürür.

Aşağıdaki kurallar kısadır. Self-hosted runner'ları yalnızca özel depolar için kullanın. Eğer herkese açık bir depoya bağlamanız gerekiyorsa, üzerinde fork pull request'lerini çalıştırmayın, o sunucuda başka hiçbir şey tutmayın ve makineyi atılabilir olarak değerlendirin.

Docker işleri ve aslında root olan grup

Container işleri, servis container'ları ve docker build komutunu çağıran her türlü iş akışı adımı, runner sunucusu üzerinde bir Docker daemon'ına ihtiyaç duyar. Docker'ı, VPS üzerinde Docker ve Docker Compose rehberinde anlatıldığı şekilde kurun ve ardından runner kullanıcısını docker grubuna ekleyin.

Bu işlemi yapmadan önce getireceği riskleri anlayın. docker grubuna üyelik, root yetkisine eşdeğerdir; çünkü bir container, / dizinini bind mount edebilir ve içerisinde root olarak çalışabilir. Dolayısıyla, Docker socket ile iletişim kurabilen bir iş akışı, /etc/shadow dahil olmak üzere VPS üzerindeki her dosyayı okuyabilir ve değiştirebilir. Güvenilir katkıcıların bulunduğu özel bir depoda bu kabul edilebilir bir bedel olabilir. Bunun dışındaki durumlarda ise, ayrıcalıksız kullanıcı kullanmanın bir anlamı kalmaz. Rootless Docker, container derlemelerini runner kullanıcısının kendi yetkileri dahilinde tutar; ancak bunun bedeli, daha yavaş bir depolama sürücüsü ve ayrıcalıklı container'ların kullanılamamasıdır.

Güncellemeler ve runner'ın temiz bir şekilde kaldırılması

Self-hosted runner, varsayılan olarak kendini günceller. Yeni bir sürüm algıladığında kendi dosyalarını değiştirir ve servisi yeniden başlatır; bu nedenle normal şartlarda herhangi bir işlem yapmanız gerekmez. ./config.sh --disableupdate, sabit bir sürüme ihtiyaç duyduğunuz durumlarda otomatik güncellemeyi devre dışı bırakır. Bu durumda güncelleme sorumluluğu size aittir: GitHub dokümantasyonu, --disableupdate ile yapılandırılmış bir runner'ın manuel olarak güncellenmesi gerektiğini açıkça belirtir.

Manuel güncelleme işlemi kayıt bilgilerini korur; çünkü .runner ve .credentials dosyaları tarball içerisinde yer almaz. Servisi durdurun, yeni tarball dosyasını indirin ve gharunner ile sağlama toplamını (checksum) doğrulayın, tar xzf kullanarak aynı dizine çıkartın ve ardından servisi tekrar başlatın:

cd /home/gharunner/actions-runner
sudo ./svc.sh stop
sudo ./svc.sh start

Runner'ı kaldırmak için önce servisi kaldırın, ardından kaydını silin. Kaldırma belirteci (token), aynı Runners sayfasında, runner'ın kendi Remove butonu 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_HERE

Kaydı silmeden dizini silerseniz, runner depoda Offline olarak listelenmeye devam eder. Çünkü GitHub, runner'ın silindiğini ancak runner bildirimde bulunduğunda veya bir yönetici girişi manuel olarak sildiğinde öğrenebilir.

Hata modları ve karşılaşacağınız dizgeler

Must not run with sudo. config.sh, root kullanıcısı olarak çalıştırıldığında bu mesajı yazdırır ve sonlanır. Bu denetim kasıtlıdır; çünkü _work dizinindeki root sahipli dosyalar, servis 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 devre dışı bırakır ancak bunu kullanmak sorunu yalnızca ileri bir aşamaya taşır.

sudo: ./svc.sh: command not found. Doğru dizindesiniz. config.sh henüz bir kayıt işlemini tamamlamadığı için svc.sh henüz mevcut değildir. Runner'ı kaydedin, ardından servisi kurun.

Http response code: NotFound from 'POST https://api.github.com/actions/runner-registration'. Belirteç geçerli bir kayıt belirteci değildir. Belirteçlerin geçerlilik süresi bir saat olduğu için süresi dolmuş olabilir veya Runners sayfasından alınan kayıt belirteci yerine bir kişisel erişim belirteci (personal access token) yapıştırılmış olabilir. Yeni bir belirteç oluşturun ve tekrar 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 tekrar kaydedin.

Yeniden başlatma sonrası Runner Çevrimdışı (Offline). systemctl is-enabled 'actions.runner.*' komutunu çalıştırın. Listede hiçbir şey görünmüyorsa ./svc.sh install hiç çalıştırılmamıştır; bu durumda runner yalnızca terminal oturumunuzun içinde var olmuştur. Eğer birim etkinleştirilmişse ve runner hala Çevrimdışı görünüyorsa journalctl -u 'actions.runner.*' dosyasını okuyun ve giden HTTPS trafiğini denetleyin.

Disk doluyor. Checkout işlemleri, derleme önbellekleri ve Docker imajları _work altında ve runner kullanıcısının ev dizininde birikir; sistem bunları sizin yerinize temizlemez. du -sh /home/gharunner/actions-runner/_work dizinini izleyin ve disk dolmadan önce zamanlanmış bir temizlik görevi ekleyin.

FAQ

sudo ./svc.sh install neden "command not found" hatası veriyor?

Çünkü svc.sh, runner tarball içerisinde bulunmaz. Bu dosya, ./config.sh kaydı tamamladığında, servis adını oluşturmak için depo ve runner adınızı kullanarak runner dizininde üretilir. İlk olarak ./config.sh komutunu runner kullanıcısı ile çalıştırın. Bu işlemden sonra sudo ./svc.sh install gharunner betiği bulur ve /etc/systemd/system dizini içerisine actions.runner.OWNER-REPO.RUNNER-NAME.service adında bir unit dosyası yazar.

Self-hosted runner için güvenlik duvarında port açmam gerekiyor mu?

Hayır. Runner, GitHub'a giden bir HTTPS bağlantısı başlatır ve iş 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 kurallarınızı kapalı tutun. Servis çalışır durumdayken runner "Offline" görünüyorsa, gelen kurallardan ziyade giden trafik filtrelemesine ve DNS ayarlarına bakın.

Self-hosted runner'ı herkese açık bir depoda kullanabilir miyim?

Kullanabilirsiniz, ancak GitHub bunu önermemektedir. Bir fork üzerinden gelen pull request kendi iş akışı dosyasını taşır; bu nedenle deponuzu fork edebilen herkes, makinenizde çalışacak komutlar önerebilir. Onay istemi yalnızca bir katılımcının ilk çalıştırılmasını kapsar. Eğer bir runner'ı herkese açık bir depoya bağlarsanız, üzerinde fork pull request iş akışlarını devre dışı bırakın, o sunucuda başka hiçbir şey barındırmayın ve makineyi düzenli aralıklarla yeniden oluşturun.

Kayıt işlemi neden Http response code: NotFound hatasıyla başarısız oluyor?

Kayıt çağrısı, yalnızca URL yanlış olduğunda değil, kimlik bilgileri hatalı olduğunda da NotFound yanıtı döner; bu durum hata mesajını yanıltıcı kılar. Kayıt token'ları gösterildikten bir saat sonra geçerliliğini yitirir ve bu çağrı için kişisel erişim token'ı (personal access token) kabul edilmez. Ayarlar (Settings), Actions, Runners, New self-hosted runner adımlarını izleyerek yeni bir token alın ve --url değerinin yönetici haklarına sahip olduğunuz bir depoyu işaret ettiğinden emin olun.

#github-actions#ci#self-hosted#runner#ubuntu-24-04