Pasang GitHub Actions Runner di VPS Ubuntu 24.04
Panduan memasang self-hosted runner GitHub Actions di Ubuntu 24.04 dengan user khusus, verifikasi checksum, config.sh, service systemd, dan risiko pull request fork.
Fungsi runner GitHub Actions yang di-host sendiri
Runner GitHub Actions yang di-host sendiri adalah program yang Anda instal di VPS milik sendiri. Program ini meminta job kepada GitHub lalu menjalankannya pada hardware Anda. Anda mendaftarkannya ke satu repository, menginstalnya sebagai service systemd, dan runner akan kembali aktif setiap kali reboot. GitHub menjadwalkan job. Server Anda yang menjalankannya.
CI (continuous integration) pada mesin milik sendiri bermanfaat karena dua alasan. Menit build tidak lagi dihitung berdasarkan penggunaan, dan job dapat mengakses sumber daya yang hanya tersedia di mesin Anda, seperti cache build yang sudah terisi atau jaringan privat. Konsekuensinya adalah risiko keamanan. Runner menjalankan apa pun yang ditentukan dalam file workflow, sebagai user yang Anda berikan. Karena itu, file workflow pada dasarnya dirancang untuk menjalankan kode dari jarak jauh. Pada repository privat, hal ini dapat diterima karena hanya orang yang Anda percaya yang dapat menambahkan workflow. Pada repository publik, hal ini merupakan risiko nyata. Bagian tentang pull request dari fork menjelaskan mekanismenya.
Semua langkah berikut menggunakan Ubuntu 24.04 dengan runner versi 2.338.0, yaitu rilis terbaru per 12 October 2026.
Yang Anda perlukan sebelum memulai
Mulai dengan VPS yang memiliki akun admin biasa dan sudo, dalam kondisi yang dicapai pada sepuluh menit pertama di VPS baru. Anda tidak perlu membuka port masuk. Runner membuka koneksi HTTPS (hypertext transfer protocol secure) keluar ke GitHub dan mempertahankan koneksi tersebut tetap terbuka sambil menunggu tugas. Dengan demikian, GitHub tidak pernah terhubung ke server Anda. Firewall dapat tetap menutup akses dari dunia luar, dan tugas tetap masuk.
Anda juga memerlukan hak admin pada repositori, karena token pendaftaran ditampilkan di pengaturan repositori.
Buat pengguna khusus untuk runner
Jangan pernah menjalankan runner sebagai root atau sebagai pengguna admin Anda sendiri. Setiap job mewarisi hak pengguna runner, sehingga workflow yang memanggil sudo akan berhasil jika pengguna runner dapat menggunakan sudo. Buat satu pengguna tanpa hak istimewa yang tidak memiliki apa pun selain direktori home-nya sendiri. Akun pengguna dengan hak minimum pada VPS membahas pola umum ini. Berikut konfigurasi khususnya.
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 mengunci kata sandi, sehingga tidak ada yang dapat masuk sebagai gharunner menggunakan kata sandi tersebut. Mode 700 pada direktori runner penting karena runner menyimpan kredensialnya dalam teks biasa di sana, dan checkout dapat berisi kode sumber privat.
Periksa kedua properti tersebut sebelum melanjutkan:
sudo passwd -S gharunner
sudo -l -U gharunnerpasswd -S mencetak baris yang diawali gharunner L, dengan L yang berarti kata sandi terkunci. sudo -l -U gharunner seharusnya menampilkan is not allowed to run sudo. Jika yang ditampilkan adalah daftar perintah yang diizinkan, akun tersebut berada dalam grup sudo dan isolasi yang baru saja Anda buat sudah tidak berlaku.
Unduh runner dan periksa tarball
Mulai dari sini, gunakan user runner.
sudo -iu gharunner
cd ~/actions-runner
RUNNER_VERSION=2.338.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"Jalankan uname -m terlebih dahulu jika Anda tidak yakin dengan arsitekturnya. x86_64 menggunakan file linux-x64 di atas. aarch64 menggunakan actions-runner-linux-arm64-${RUNNER_VERSION}.tar.gz.
Sekarang verifikasi file yang Anda unduh. Nilai SHA256 (secure hash algorithm, 256 bit) di bawah ini adalah untuk tarball x64 versi 2.338.0. GitHub menampilkan nilai untuk rilis saat ini pada halaman rilis dan layar New self-hosted runner. Nilai tersebut berubah pada setiap versi. Karena itu, salin nilai dari sana saat menginstal versi yang berbeda.
echo "af4b794c1bc41d73d40535e3fe092a39f9679cd8d965954c2aca25a05ca41d32 actions-runner-linux-x64-2.338.0.tar.gz" | sha256sum -cUnduhan yang benar menampilkan satu baris:
actions-runner-linux-x64-2.338.0.tar.gz: OKFile yang terpotong atau diubah menampilkan kegagalan dan peringatan:
actions-runner-linux-x64-2.338.0.tar.gz: FAILED
sha256sum: WARNING: 1 computed checksum did NOT matchJangan lewati pemeriksaan ini dan membiarkan tar menemukan masalahnya. Arsip yang ditulis secara tidak lengkap akan gagal dengan gzip: stdin: unexpected end of file dan tar: Unexpected EOF in archive. Pesan tersebut menunjukkan bahwa file rusak, tetapi tidak menunjukkan apakah file terpotong atau diganti.
tar xzf ./actions-runner-linux-x64-2.338.0.tar.gz
lsIsi tarball dan yang tidak disertakannya
Setelah diekstrak, direktori tersebut berisi config.sh, run.sh, env.sh, safe_sleep.sh, bin/, dan externals/. bin/ berisi biner runner dan bin/installdependencies.sh. externals/ berisi runtime Node yang disertakan, yang digunakan untuk menjalankan action JavaScript.
svc.sh belum tersedia. Dokumentasi GitHub menjelaskannya sebagai skrip "yang dibuat setelah runner berhasil ditambahkan", karena skrip tersebut ditulis dari template dengan nama repositori dan runner Anda yang disertakan dalam nama service. Jadi, sudo ./svc.sh install sebelum ./config.sh gagal dengan sudo: ./svc.sh: command not found. Lakukan registrasi terlebih dahulu, lalu instal service.
Instal dependensi runner
Runner adalah aplikasi .NET, sehingga memerlukan beberapa shared library. Tetap gunakan shell milik pengguna runner dan instal library tersebut dengan sudo, karena skrip menulis ke database paket sistem.
exit
cd /home/gharunner/actions-runner
sudo ./bin/installdependencies.shPada Ubuntu 24.04, perintah tersebut menginstal libkrb5-3, zlib1g, liblttng-ust1t64, libssl3t64, dan libicu74. Skrip mencoba beberapa nama versi untuk setiap library dan menggunakan nama yang tersedia pada rilis Anda. Karena itu, skrip yang sama dapat digunakan pada Ubuntu versi lama dan Debian.
Jika Anda melewati langkah ini dan salah satu library tersebut tidak tersedia, ./config.sh berhenti sebelum melakukan apa pun:
Dependencies is missing for Dotnet Core 6.0
Execute sudo ./bin/installdependencies.sh to install any missing Dotnet Core 6.0 dependencies.libicu yang tidak tersedia menampilkan saran yang sama dengan baris pertama yang berbeda, yaitu Libicu's dependencies is missing for Dotnet Core 6.0. Keduanya berasal dari sumber yang sama: config.sh memeriksa library yang disertakan dengan ldd, lalu libicu memeriksanya dengan ldconfig sebelum skrip dimulai. Karena itu, tautan yang tidak dapat diselesaikan membuat skrip berhenti, bukan menghasilkan crash yang sulit dipahami kemudian.
Daftarkan runner ke repositori
Dapatkan token dari repositori. Buka Settings, lalu Actions, lalu Runners, kemudian New self-hosted runner. Halaman tersebut menampilkan registration token. Token ini kedaluwarsa satu jam setelah dibuat, jadi buat token saat Anda siap menempelkannya.
Lakukan pendaftaran sebagai pengguna runner. config.sh menolak dijalankan dengan sudo.
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 \
--replaceFungsi setiap flag tersebut. --name menentukan nama runner yang ditampilkan di repositori, jadi pilih nama yang masih dapat Anda kenali enam bulan kemudian. --labels menambahkan label Anda sendiri; runner sudah memiliki self-hosted, Linux, dan X64 tanpa perlu diminta. --work menentukan direktori tempat checkout disimpan, di dalam direktori runner. --unattended menjawab prompt interaktif menggunakan nilai defaultnya. Ini diperlukan ketika perintah tersebut ditempatkan dalam skrip. --replace mengambil alih registration yang sudah ada dengan nama yang sama, alih-alih gagal. Ini diperlukan ketika Anda membangun ulang server.
Proses yang berhasil berakhir dengan baris berikut:
√ Runner successfully added
√ Runner connection is good
√ Settings Saved.Registration kini tersimpan di direktori runner sebagai .runner, .credentials, dan .credentials_rsaparams. Dua yang terakhir digunakan untuk mengidentifikasi runner ini ke GitHub, sehingga siapa pun yang dapat membacanya dapat menyamar sebagai runner tersebut. Karena itu, direktori menggunakan mode 700 dan pengguna tersebut tidak memiliki akses sudo.
Instal runner sebagai layanan systemd
./run.sh di terminal cukup untuk satu pengujian, tetapi proses tersebut berhenti saat sesi SSH berakhir. Instal layanan ini agar runner dimulai saat boot. layanan dan timer systemd pada VPS menjelaskan file unit itu sendiri. Di sini, svc.sh membuatkannya untuk Anda.
exit
cd /home/gharunner/actions-runner
sudo ./svc.sh install gharunner
sudo ./svc.sh start
sudo ./svc.sh statussvc.sh memerlukan root karena menulis unit ke /etc/systemd/system dan mengaktifkannya. Argumen setelah install adalah pengguna yang menjalankan layanan. Berikan gharunner secara eksplisit. Tanpa argumen, skrip menggunakan $SUDO_USER, yaitu akun administrator Anda, sehingga setiap tugas berjalan sebagai pengguna yang dapat menggunakan sudo.
Unit tersebut diberi nama berdasarkan repositori dan runner, dalam format actions.runner.YOUR-USER-YOUR-REPO.vps-runner-1.service. Anda tidak perlu mengetikkannya:
systemctl list-units 'actions.runner.*'
sudo journalctl -u 'actions.runner.*' -n 20 --no-pagerRunner yang berjalan normal mencatat √ Connected to GitHub, lalu baris yang diakhiri dengan Listening for Jobs. Halaman Runners pada repositori akan menampilkannya sebagai Idle. Runner yang ditampilkan sebagai Offline berarti layanan tersebut tidak berjalan atau tidak dapat terhubung ke GitHub melalui port 443.
Kirim tugas ke runner
runs-on memilih runner berdasarkan label. Minta self-hosted dan label Anda sendiri agar tugas tidak dijalankan pada runner yang tidak dimaksud.
name: build
on:
push:
branches: [main]
jobs:
build:
runs-on: [self-hosted, linux, vps]
steps:
- uses: actions/checkout@v5
- run: uname -aJika tugas menunggu di Waiting for a runner to pick up this job, labelnya tidak cocok. Setiap label di runs-on harus ada pada runner. Satu kata tambahan dapat membuat tugas tetap berada dalam antrean tanpa kesalahan di mana pun. Bandingkan daftar tersebut dengan label yang ditampilkan di samping runner pada pengaturan repositori.
Mengapa self-hosted runner dan repository publik tidak dapat digunakan bersamaan
Ini adalah bagian yang sering dilewati. Panduan GitHub menyatakannya dengan tegas: self-hosted runner "should almost never be used for public repositories", dan runner tersebut "do not have guarantees around running in ephemeral clean virtual machines, and can be persistently compromised by untrusted code in a workflow".
Mekanismenya sederhana. Pull request dari fork membawa salinan file workflow-nya sendiri. Jika repository publik Anda menjalankan workflow pull request pada runner Anda, siapa pun yang dapat melakukan fork pada repository tersebut dapat mengusulkan workflow yang menjalankan perintah mereka di VPS Anda. Mereka tidak memerlukan akses tulis, karena hal yang mereka usulkan itulah yang dijalankan.
Pengaturan persetujuan hanya mengurangi risiko ini, bukan menyelesaikannya. Kebijakan default untuk repository publik meminta maintainer menyetujui workflow fork dari kontributor untuk pertama kalinya. GitHub menganggap seseorang sebagai kontributor untuk pertama kalinya hanya sampai commit atau pull request mereka digabungkan ke dalam repository. Setelah itu, pull request mereka berikutnya berjalan tanpa permintaan persetujuan, dan GitHub sendiri memperingatkan bahwa perbaikan sederhana yang digabungkan sudah cukup untuk mencapai tahap tersebut. Jadi, pengamannya adalah pemeriksaan diff oleh manusia, sedangkan payload yang disembunyikan tiga tingkat di dalam build script mudah terlewat.
Pull request dari fork tidak menerima secret Anda, dan GITHUB_TOKEN bersifat read only. Hal ini membatasi dampak di dalam GitHub. Namun, hal ini tidak melindungi server Anda. Penyerang memperoleh shell sebagai gharunner. Mereka dapat membaca setiap file yang dapat dibaca oleh user tersebut, mengakses apa pun yang dapat dijangkau VPS melalui jaringan privatnya, dan meninggalkan sesuatu di ~/.bashrc atau pada user systemd unit yang berjalan saat job berikutnya.
Mendaftarkan runner dengan --ephemeral membuat runner menerima satu job lalu melakukan deregister, sehingga satu job tidak dapat membaca workspace job berikutnya. Cara ini hanya membantu jika ada proses yang membangun ulang mesin atau container untuk setiap job. Backdoor yang ditulis ke home directory milik user runner tetap bertahan setelah pendaftaran ulang.
Aturan berikut ini singkat. Gunakan self-hosted runner untuk repository privat. Jika Anda harus menghubungkannya ke repository publik, jangan jalankan pull request dari fork di sana, jangan letakkan hal lain pada server tersebut, dan perlakukan mesin itu sebagai perangkat yang dapat dibuang.
Pekerjaan Docker dan grup yang pada dasarnya setara dengan root
Pekerjaan dalam container, container layanan, dan langkah alur kerja apa pun yang memanggil docker build memerlukan daemon Docker pada host runner. Instal Docker dengan cara yang umum, seperti yang dijelaskan dalam Docker dan Docker Compose pada VPS, lalu tambahkan pengguna runner ke grup docker.
Pahami risikonya sebelum melakukannya. Keanggotaan dalam grup docker setara dengan root karena container dapat melakukan bind mount pada / dan berjalan sebagai root di dalamnya. Jadi, alur kerja yang dapat berkomunikasi dengan socket Docker dapat membaca dan menulis setiap file di VPS, termasuk /etc/shadow. Pada repositori privat dengan kontributor tepercaya, risiko ini mungkin dapat diterima. Di lingkungan lain, hal ini menghilangkan manfaat pengguna tanpa hak istimewa. Docker rootless membatasi build container pada hak akses pengguna runner, tetapi menggunakan driver penyimpanan yang lebih lambat dan tidak mendukung container dengan hak istimewa.
Pembaruan dan penghapusan runner secara bersih
Secara default, self-hosted runner memperbarui dirinya sendiri. Runner mendeteksi release baru, mengganti file miliknya, lalu me-restart service. Biasanya, Anda tidak perlu melakukan apa pun. Untuk menonaktifkan pembaruan otomatis, teruskan --disableupdate ke ./config.sh saat mendaftarkan runner. Pada runner yang sudah terdaftar, daftarkan runner tersebut kembali menggunakan flag itu. Pengaturan ini tidak memungkinkan Anda menggunakan satu versi selamanya. Dokumentasi GitHub menjelaskan bahwa runner yang dikonfigurasi dengan --disableupdate tetap harus diperbarui secara manual dalam 30 hari setelah release baru tersedia. Jika tidak, GitHub berhenti mengantrekan job untuk runner tersebut.
Pembaruan manual mempertahankan registrasi karena .runner dan .credentials tidak terdapat dalam tarball. Hentikan service, unduh dan periksa checksum tarball baru sebagai gharunner, ekstrak tarball tersebut ke direktori yang sama menggunakan tar xzf, lalu jalankan kembali service:
cd /home/gharunner/actions-runner
sudo ./svc.sh stop
sudo ./svc.sh startUntuk menghapus runner, hapus instalasi service terlebih dahulu, lalu batalkan registrasinya. Token penghapusan tersedia pada halaman Runners yang sama, di bawah tombol Remove milik runner tersebut.
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_HEREMenghapus direktori tanpa membatalkan registrasi membuat runner tetap tercantum sebagai Offline di repositori. Runner tersebut tetap tercantum sampai admin mengeklik Force remove this runner, atau sampai GitHub menghapusnya secara otomatis setelah 14 hari tanpa koneksi.
Mode kegagalan dan string yang akan Anda lihat
Must not run with sudo. config.sh mencetak pesan ini lalu keluar ketika dijalankan sebagai root. Pemeriksaan ini disengaja karena file milik root di _work akan menggagalkan setiap job berikutnya yang dijalankan sebagai pengguna service. Jalankan ./config.sh sebagai gharunner. Variabel RUNNER_ALLOW_RUNASROOT mengesampingkan pemeriksaan ini, tetapi penggunaannya hanya menunda kegagalan.
sudo: ./svc.sh: command not found. Anda berada di direktori yang benar. svc.sh belum ada karena config.sh belum menyelesaikan proses pendaftaran. Daftarkan runner, lalu instal service.
Http response code: NotFound from 'POST https://api.github.com/actions/runner-registration'. Biasanya token tersebut bukan token pendaftaran yang valid. Token itu mungkin sudah kedaluwarsa karena masa berlakunya hanya satu jam, atau personal access token ditempelkan sebagai pengganti token pendaftaran dari halaman Runners. Buat token baru dan tempelkan kembali. Periksa juga bahwa --url merupakan URL repositori lengkap.
Dependencies is missing for Dotnet Core 6.0. Jalankan sudo ./bin/installdependencies.sh dari direktori runner sebagai root, lalu lakukan pendaftaran kembali.
Runner Offline setelah reboot. Jalankan systemctl is-enabled 'actions.runner.*'. Jika tidak ada yang tercantum, ./svc.sh install belum pernah dijalankan. Artinya, runner hanya pernah berjalan di dalam sesi terminal Anda. Jika unit diaktifkan tetapi runner masih Offline, baca journalctl -u 'actions.runner.*' dan periksa koneksi HTTPS keluar.
Disk penuh. Checkout, cache build, dan image Docker menumpuk di _work serta di home pengguna runner. Tidak ada proses yang membersihkannya secara otomatis. Pantau du -sh /home/gharunner/actions-runner/_work dan tambahkan pembersihan terjadwal sebelum disk penuh.
FAQ
Mengapa sudo ./svc.sh install menampilkan pesan command not found?
Karena svc.sh tidak ada dalam tarball runner. File tersebut dibuat di direktori runner setelah ./config.sh selesai melakukan registrasi, dengan menggunakan nama repository dan runner untuk membentuk nama service. Jalankan ./config.sh terlebih dahulu sebagai pengguna runner. Setelah itu, sudo ./svc.sh install gharunner menemukan script tersebut dan menulis unit bernama actions.runner.OWNER-REPO.RUNNER-NAME.service ke dalam /etc/systemd/system.
Apakah saya perlu membuka port firewall untuk runner yang di-host sendiri?
Tidak. Runner membuka koneksi HTTPS keluar ke GitHub dan mempertahankannya selama menunggu job, sehingga GitHub tidak pernah memulai koneksi ke VPS Anda. Izinkan koneksi keluar pada port 443 dan tetap tutup aturan koneksi masuk. Jika runner menampilkan status Offline saat servicenya berjalan, periksa pemfilteran koneksi keluar dan DNS, bukan aturan koneksi masuk.
Apakah saya dapat menggunakan runner yang di-host sendiri pada repository publik?
Anda dapat melakukannya, tetapi GitHub tidak menyarankannya. Pull request dari fork membawa file workflow-nya sendiri, sehingga siapa pun yang dapat melakukan fork terhadap repository Anda dapat mengusulkan command yang berjalan pada mesin Anda. Prompt persetujuan default hanya mencakup kontributor yang belum pernah memiliki commit atau pull request yang di-merge ke dalam repository tersebut. Jika Anda menghubungkan runner ke repository publik, nonaktifkan workflow pull request dari fork pada runner tersebut, jangan jalankan hal lain di server itu, dan rebuild mesin secara berkala.
Mengapa registrasi gagal dengan Http response code: NotFound?
Panggilan registrasi memberikan respons NotFound ketika credential salah, bukan hanya ketika URL salah, sehingga pesannya dapat menyesatkan. Token registrasi kedaluwarsa satu jam setelah ditampilkan, dan personal access token tidak diterima untuk panggilan ini. Buka kembali Settings, Actions, Runners, New self-hosted runner, salin token terbaru, lalu pastikan nilai --url mengarah ke repository tempat Anda memiliki hak administrator.