blog
GitHub Actions ile kendi VPS’ine dağıtım: dört yaklaşım
Yayın tarihi
GitHub Actions’tan kendi sunucuna dağıtmanın her yolu CI’a bir tür sır verir. İşe yarar karşılaştırma şu: CI hesabın ya da tek bir workflow ele geçirilirse, o sır birine neyi yaptırır?
Yaklaşım 1: workflow’dan SSH
Workflow bir özel anahtar tutar, sunucuya bağlanır ve dağıtımı çalıştırır. Kurması en kolay olanı; kısıtlanmamış bir anahtarla da etki alanı en geniş olanı: anahtarı ele geçiren bir kabuk elde eder.
İki şey onu çok daha güvenli yapar. Sunucunun host anahtarını, cevap veren her şeye güvenmek yerine bir secret’ta sabitle ve anahtarı sunucuda tam olarak tek bir komut çalıştırabilecek şekilde kısıtla.
Workflow adımı
- name: Deploy over SSH
env:
SSH_KEY: ${{ secrets.DEPLOY_SSH_KEY }}
KNOWN_HOSTS: ${{ secrets.DEPLOY_KNOWN_HOSTS }}
run: |
install -m 700 -d ~/.ssh
echo "$SSH_KEY" > ~/.ssh/deploy && chmod 600 ~/.ssh/deploy
echo "$KNOWN_HOSTS" > ~/.ssh/known_hosts
ssh -T -i ~/.ssh/deploy deploy@203.0.113.10DEPLOY_KNOWN_HOSTS değerini zaten güvendiğin bir oturumdan doldur ve sunucuda ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub çıktısıyla karşılaştır. Workflow içinde ssh-keyscan çalıştırmak, o gün cevap veren her şeye güvenmek demektir.
Anahtarı sunucuda kısıtla
Dağıtım kullanıcısının ~/.ssh/authorized_keys dosyasında zorunlu bir komut ve restrict, anahtarı tek amaçlı bir tetikleyiciye çevirir. İstemci hangi komutu isterse istesin sunucu script’i çalıştırır; port yönlendirme, agent yönlendirme ve terminal reddedilir.
Script’i istemcinin gönderdiği şeyden bağımsız tut. İstenen komut ona SSH_ORIGINAL_COMMAND olarak ulaşır; bir gün okuyacaksan sıkı doğrula.
command="/opt/app/deploy.sh",restrict ssh-ed25519 AAAAC3Nz... github-actions
Bu satırla, sızan bir anahtar bir dağıtım başlatabilir, başka hiçbir şey yapamaz. Bu satır olmadan aynı anahtar bir giriş hakkıdır.
Yaklaşım 2: self-hosted runner
GitHub’ın runner’ını sunucuya kurarsın ve workflow doğrudan orada, Docker’a ve yerel dosya sistemine erişimle çalışır. Hiçbir SSH anahtarı sunucudan çıkmaz ve derlemeler yerel önbelleği kullanabilir.
Bedeli, workflow kodunun artık üretim makinende, runner hangi kullanıcıysa o kullanıcı olarak çalışması. GitHub self-hosted runner’ları yalnızca özel depolar için öneriyor; çünkü herkese açık bir depoda bir fork’tan gelen pull request kendi workflow’unu senin makinende çalıştırmayı deneyebilir. Docker daemon’ıyla konuşabilen bir runner da, yine, root’tur.
Yaklaşım 3: registry’ye gönder, sonra webhook çağır
CI imajı derleyip bir registry’ye gönderir. Sonra sunucundaki küçük bir servise tek bir HTTP isteği yollar; o servis yeni imajı kendi salt okunur kimlik bilgisiyle çeker ve uygulamayı yeniden başlatır. CI hiçbir zaman SSH erişimi tutmaz, sunucu da sabit bir eylem kümesi dışında hiçbir şey kabul etmez.
Bedava bir geçiş değil: registry’ne gönderebilen kişi sırada neyin çalışacağına karar verir. Ama sızan bir webhook sırrı, birinin giriş yapmasına değil, zaten orada olanı yeniden dağıtmasına izin verir.
Derle, GHCR’a gönder, sonra sunucudan çekmesini iste
permissions:
contents: read
packages: write
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: docker/build-push-action@v6
with:
push: true
tags: ghcr.io/example/app:latest
- name: Ask the server to pull
env:
DEPLOY_URL: ${{ secrets.DEPLOY_URL }}
DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}
run: |
curl --fail --silent --show-error -X POST "$DEPLOY_URL" \
-H "Authorization: Bearer $DEPLOY_TOKEN"Yaklaşım 4: derleme çıktısını rsync’le
Statik bir sitede yeniden başlatılacak bir şey yok: CI’da derle ve dosyaları web sunucusunun sunduğu dizine kopyala. SSH üzerinden rsync yalnızca değişeni gönderir.
Yaklaşım 1’deki anahtar kaygısı burada da geçerli, benzer bir çözüm de var: rsync, zorunlu komut olarak kullanıldığında anahtarı tek bir dizine hapseden rrsync adlı bir yardımcıyla gelir. Kurulum yolu dağıtımdan dağıtıma değişir. Süreci ya da veritabanı olan her şeyde dosya kopyalamak dağıtımın yalnızca yarısıdır; yeniden başlatma yarısı seni yaklaşım 1’e geri götürür.
authorized_keys: bu anahtar yalnızca /var/www/site dizinine ulaşabilir
command="rrsync /var/www/site",restrict ssh-ed25519 AAAAC3Nz... github-actions
Yaklaşım yaklaşım hangi sır sızıyor
- 01Kısıtsız SSH anahtarı: sunucuda bir kabuk. Zorunlu komutla: tek bir dağıtım eylemi.
- 02Self-hosted runner: hiçbir şeyin sızmasına gerek yok — ele geçirilmiş workflow kodu zaten sunucuda çalışıyor.
- 03Registry artı webhook: yeniden dağıtma yetkisi, artı registry token’ının izin verdikleri. Asıl ödül registry’ye gönderme yetkisi.
- 04rrsync ile sınırlanmış rsync: tek bir sitenin dosyalarına yazma yetkisi.
Kimlik doğrulamasız bir dağıtım adresi bir uzaktan kumandadır
Webhook’u karşılayan şey isteğin kimliğini doğrulamak zorunda. Açık bir dağıtım adresi herkesin istediği an yeniden başlatma tetiklemesine izin verir; parametre olarak etiket ya da dal kabul eden bir adres ise neyin çalışacağını seçmelerine.
Sırrı sorgu dizesinde değil, bir başlıkta gönder — sorgu dizeleri erişim loglarına, proxy loglarına ve kabuk geçmişine düşer. Daha iyisi, GitHub’ın kendi webhook’larını X-Hub-Signature-256 ile imzaladığı gibi istek gövdesini HMAC ile imzala ve imzayı sunucuda sabit zamanlı karşılaştır.
Yaklaşım 3’ün MihraOps sürümü
MihraOps yaklaşım 3’ü isteğe bağlı bir Agent ile uygular: sunucunda mihraops-agent adlı bir konteyner. Her uygulama /hooks/<name> adresinde kendi tetikleyicisini alır; bir bearer token ya da GitHub tarzı HMAC imzasıyla doğrulanır ve doğrulamayı geçemeyen bir istek hiçbir şeyi değiştirmez.
Dağıtım webhook’ları Pro’nun parçası. Workflow adımı ve token kurulumu doküman sayfasında.