İlk otomatik deploy'unuzu kurmak genellikle bir öğleden sonra sürer. Bir deploy.yml dosyası yazarsınız, sunucunun root parolasını ya da anahtarını GitHub Secrets'a koyarsınız, main dalına push edersiniz ve Actions sekmesinde yeşil tik belirir. O an her şey mükemmel görünür.
Sorun ikinci ayda çıkar. Bir gece iki kişi aynı anda push eder, iki iş paralel çalışır ve dosyalar birbirinin üstüne yarım yamalak yazılır. Ya da rsync --delete bir gün uploads/ klasörünü olduğu gibi siler ve müşterinin üç yıllık ürün görselleri gider. Ya da workflow yeşil kalır ama site 502 döner ve elinizde "önceki hâle dönme" diye bir düğme yoktur; tek çareniz git'te bir commit geri alıp yeniden deploy tetiklemek, yani arızayı en az beş dakika daha ayakta tutmaktır.
Bu rehber actions/checkout ve rsync satırlarını kopyalamanızı anlatan bir README çevirisi değil. O kısmı zaten her yerde bulursunuz. Burada ağırlık iki şeyde: sunucudaki yetki yüzeyini deploy'un gerçekten ihtiyaç duyduğu kadarına indirmek ve kötü bir deploy'dan tek komutla dönebilmek. Sonunda elinizde, çalındığında bile sunucunuza shell açamayan bir anahtar, silmemesi gereken dizinlere dokunamayan bir rsync ve saniyeler içinde önceki sürüme dönen bir symlink olacak.
Neden root ile Bağlanmamalısınız: Ayrı Bir deploy Kullanıcısı#
Bir CI sisteminin sunucunuzda yapabileceği en fazla iş, dosya kopyalamak ve bir servisi yeniden başlatmaktır. Buna karşılık root anahtarı verdiğinizde CI'ya kullanıcı silme, disk biçimlendirme, iptables kuralı değiştirme yetkisi de vermiş olursunuz. GitHub hesabınızın ele geçirilmesi ya da bir bağımlılığın (action) zehirlenmesi durumunda bu farkın bedeli çok büyüktür.
Ayrı bir kullanıcı açın ve ona shell parolası vermeyin:
# Parolasız, yalnızca anahtarla girilebilen bir servis hesabı
sudo adduser --disabled-password --gecos "" deploy
# Uygulama dizini deploy'a ait, web sunucusu grubu okuyabilsin
sudo mkdir -p /var/www/uygulama/{releases,shared}
sudo chown -R deploy:www-data /var/www/uygulama
sudo chmod -R 2750 /var/www/uygulama
2750 içindeki setgid biti (ilk hane 2) önemlidir: alt dizinler otomatik olarak www-data grubunu devralır, böylece deploy'un yazdığı dosyaları Nginx/PHP-FPM okuyabilir ama başkası okuyamaz.
Servis yeniden başlatma için tam sudo yerine tek satırlık bir izin verin. sudo visudo -f /etc/sudoers.d/deploy ile açın:
deploy ALL=(root) NOPASSWD: /usr/bin/systemctl reload nginx, /usr/bin/systemctl restart uygulama.service
Yolun doğru olduğunu command -v systemctl ile teyit edin; sudoers'ta yanlış yol yazmak kuralı sessizce etkisiz bırakır. Dosya izni 0440 olmalıdır, aksi hâlde sudo tüm sudoers'ı reddeder.
Sunucuda git pull çalıştırmayın. Yaygın bir kalıp olsa da iki sorun getirir: sunucuya deponuzu okuyabilen bir anahtar koymuş olursunuz (sunucu düşerse kaynak kodunuz da düşer) ve web kökünde bir .git dizini oluşur. Bu dizinin dışarıdan okunabilir kalması, tüm kaynak kodunuzun ve geçmiş commit'lerdeki sırların indirilebilmesi demektir; ayrıntısı .git klasörü ve .env dosyası ifşası yazısında. Derlemeyi runner'da yapın, sunucuya yalnızca çıktıyı gönderin.
Parolasız Deploy Anahtarı Üretmek ve Nereye Koymamak Gerektiği#
CI bir passphrase yazamayacağı için deploy anahtarı zorunlu olarak parolasızdır. Bunu telafi eden şey, anahtarın ne yapabildiğinin sınırlanmasıdır — bir sonraki bölümün konusu.
Anahtarı kendi makinenizde üretin; sunucuda ya da workflow içinde üretmeyin:
ssh-keygen -t ed25519 -C "gh-actions-deploy@uygulama" -N "" -f ~/.ssh/gh_deploy_uygulama
Ed25519 seçimi bilinçli: kısa, hızlı ve modern OpenSSH sürümlerinde varsayılan olarak kabul edilir. Anahtar üretimi ve dosya izinlerinin ayrıntısı için SSH anahtarı nasıl oluşturulur yazısına bakabilirsiniz.
Üç kural:
- Bu anahtar yalnızca bu depo ve bu sunucu için olsun. Kişisel anahtarınızı Secrets'a koymayın; sızarsa bütün sunuculara erişim gider.
- Her ortam için ayrı anahtar üretin. Staging anahtarı üretime bağlanamamalı.
- Anahtarı yılda bir döndürün.
authorized_keysiçindeki satırın sonuna tarih yazarsanız hangisinin ne zaman üretildiğini hatırlarsınız.
authorized_keys'te command= ve from= ile Anahtarı Kafese Koymak#
Asıl kilit burada. Varsayılan hâlde bir authorized_keys satırı "bu anahtarla tam shell aç" demektir. OpenSSH bunu daraltmak için satır başına seçenek yazmanıza izin verir:
restrict,command="/usr/bin/rrsync -wo /var/www/uygulama/releases" ssh-ed25519 AAAAC3Nza... gh-deploy-yukleme-2026-08
restrict,command="/usr/local/bin/deploy-cmd" ssh-ed25519 AAAAC3Nza... gh-deploy-aktivasyon-2026-08
İki ayrı anahtar kullanmak, dosya yükleme ile sürüm aktivasyonunu birbirinden ayırır; biri sızarsa diğerinin yetkisi devralınmaz.
restrict (OpenSSH 7.2 ve sonrası) tek kelimeyle port yönlendirmeyi, agent forwarding'i, X11'i, PTY tahsisini ve ~/.ssh/rc çalıştırmayı kapatır. Yeni bir kısıtlama eklendiğinde onu da otomatik kapsar; tek tek no-port-forwarding,no-agent-forwarding,... yazmaktan daha güvenlidir.
command= ise anahtarın çalıştırabileceği tek komutu sabitler. Kullanıcı ne gönderirse göndersin sunucu bu komutu çalıştırır; istemcinin gönderdiği komut yalnızca $SSH_ORIGINAL_COMMAND değişkeninde metin olarak durur. rrsync, rsync paketiyle birlikte gelen ve genellikle /usr/bin/rrsync yolunda bulunan yardımcı bir betiktir: kendisine verilen dizinin dışına çıkan yolları reddeder, -wo ile yalnızca yazmaya, -ro ile yalnızca okumaya izin verir.
Aktivasyon anahtarının komutu ise sizin yazacağınız küçük bir kapı görevlisidir:
#!/bin/bash
# /usr/local/bin/deploy-cmd
set -euo pipefail
cmd="${SSH_ORIGINAL_COMMAND:-}"
case "$cmd" in
"release "*)
rel="${cmd#release }"
# Yalnızca 20260818T101500 biçimindeki damgayı kabul et
[[ "$rel" =~ ^[0-9]{8}T[0-9]{6}$ ]] || { echo "gecersiz surum etiketi" >&2; exit 2; }
exec /usr/local/bin/activate-release "$rel"
;;
"rollback")
exec /usr/local/bin/rollback-release
;;
*)
echo "izin verilmeyen komut" >&2
exit 1
;;
esac
Regex doğrulaması kozmetik değil: $SSH_ORIGINAL_COMMAND tamamen istemci kontrolündedir, doğrulamadan bir kabuk komutuna geçirmek command= kilidini boşa çıkarır.
from="..." seçeneği bağlantıyı belirli IP bloklarına kısıtlar ve teoride en güçlü katmandır. Ancak GitHub'ın barındırdığı runner'lar için pratikte kullanışlı değildir: çıkış IP havuzu binlerce CIDR'dan oluşur ve düzenli değişir (https://api.github.com/meta içindeki actions listesi). from= yazacaksanız ya kendi sunucunuzda self-hosted runner çalıştırın ya da sabit çıkış IP'si olan bir ara nokta kullanın; aksi hâlde ayda bir "bağlantı reddedildi" ile uğraşırsınız. Bu durumda güvenliği restrict + command= ikilisine yaslayın.
Kurulumdan sonra kilidi test edin — beklenen sonuç, shell açılmamasıdır:
ssh -i ~/.ssh/gh_deploy_uygulama [email protected]
# beklenen: "izin verilmeyen komut" ve kapanan bağlantı
Anahtarı GitHub Secrets'a Doğru Biçimde Koymak#
Bu adımda en sık görülen arıza Load key "...": error in libcrypto ya da invalid format hatasıdır ve neredeyse her zaman kopyala-yapıştır kaynaklıdır: dosyanın sonundaki yeni satır karakteri kaybolur ya da terminal satırları kırar.
En güvenilir yol dosyayı hiç ekrana basmadan aktarmaktır:
gh secret set DEPLOY_SSH_KEY --repo kullanici/depo < ~/.ssh/gh_deploy_uygulama
gh secret set DEPLOY_HOST --repo kullanici/depo --body "sunucu.ornek.com"
gh secret set DEPLOY_USER --repo kullanici/depo --body "deploy"
Workflow içinde dosyaya yazarken sondaki satır sonunu printf ile garantileyin:
- name: SSH anahtarını hazırla
run: |
install -m 700 -d ~/.ssh
printf '%s\n' "${{ secrets.DEPLOY_SSH_KEY }}" > ~/.ssh/deploy_key
chmod 600 ~/.ssh/deploy_key
Bilmeniz gereken üç davranış var. Birincisi, pull_request olayıyla tetiklenen ve fork'tan gelen işler secret göremez — bu bir hata değil, kasıtlı bir korumadır; deploy'u push (main) veya workflow_dispatch ile tetikleyin ve pull_request_target ile PR kodunu checkout etmekten kesinlikle kaçının. İkincisi, GitHub secret değerlerini loglarda maskeler ama bu maskeleme metin eşleşmesine dayanır; base64'lediğiniz ya da parçaladığınız bir secret maskelemeyi atlar. Üçüncüsü, environment: tanımlarsanız secret'ı o ortama bağlayabilir ve üzerine "onaylayan kişi" kuralı koyabilirsiniz — üretim deploy'u için ucuz ve etkili bir fren.
known_hosts'u Pinleyin: ssh-keyscan'i Workflow İçinde Çalıştırmayın#
İnternette en çok kopyalanan satır muhtemelen budur:
run: ssh-keyscan -H ${{ secrets.DEPLOY_HOST }} >> ~/.ssh/known_hosts
Bu satır sunucu kimliğini doğrulamaz; o an kim cevap verdiyse ona güvenir. Her koşuda sıfırdan güven kurduğu için, DNS kaçırma ya da araya girme senaryosunda saldırganın sunucusu sessizce kabul edilir ve deploy paketiniz — içindeki her şeyle birlikte — ona teslim edilir.
Doğrusu, host anahtarını bir kez, kendi makinenizde alıp secret olarak sabitlemektir:
ssh-keyscan -t ed25519 sunucu.ornek.com | tee /tmp/kh
ssh-keygen -lf /tmp/kh # parmak izini sunucuda `ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub` ile karşılaştırın
gh secret set DEPLOY_KNOWN_HOSTS --repo kullanici/depo < /tmp/kh
Workflow tarafında:
- name: known_hosts pinle
run: |
printf '%s\n' "${{ secrets.DEPLOY_KNOWN_HOSTS }}" > ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
echo -e "Host deploy-target\n HostName ${{ secrets.DEPLOY_HOST }}\n User ${{ secrets.DEPLOY_USER }}\n IdentityFile ~/.ssh/deploy_key\n StrictHostKeyChecking yes\n IdentitiesOnly yes" > ~/.ssh/config
Sunucuyu yeniden kurduğunuzda ya da host anahtarını değiştirdiğinizde deploy kırılır ve bu istenen davranıştır: uyarının anlamı ve doğru tepkisi için SSH host key değişti uyarısı yazısına bakın. Bağlantı hiç kurulamıyorsa sorun genellikle anahtar tarafındadır; Permission denied (publickey) hatasının nedenleri teşhis sırasını veriyor.
rsync --delete Tuzağı: .env ve uploads Dizinini Korumak#
--delete, kaynakta olmayan dosyaları hedefte siler. Deploy için doğru davranıştır — silinen bir JS dosyası sunucuda kalmasın istersiniz. Ama uygulamanın kendi ürettiği veriler de kaynakta yoktur: yüklenen görseller, üretilen PDF'ler, önbellek, .env.
Sık yapılan hataları ve gerçek etkilerini ayıralım:
| Durum | Sonuç |
|---|---|
--exclude='uploads/' yazılmış, --delete var | Dizin korunur; hariç tutulan yollar alıcıda silinmez |
--exclude + --delete-excluded birlikte | Dizin silinir; bu bayrak korumayı bilerek kaldırır |
| Hiç exclude yok | Dizin silinir |
Kaynak yolu boş bir değişkenden geliyor ("$SRC"/) | Hedef boşaltılır; en tehlikeli senaryo |
Kaynakta sondaki / unutulmuş | İçerik bir alt dizine kopyalanır, --delete üst seviyede temizlik yapar |
En kritik satır sondan ikincisi: rsync -a --delete "$SRC"/ hedef/ komutunda $SRC tanımsızsa rsync boş bir dizinle senkronize eder ve hedefi süpürür. Bu yüzden gönderimden önce kaynağı doğrulayın.
Hariç tutma listesini workflow'a gömmek yerine depoya bir dosya olarak koyun; böylece gözden geçirilebilir ve tek yerde durur:
# deploy/rsync-exclude.txt
.git/
.github/
node_modules/
.env
storage/
public/uploads/
Gönderim adımı:
- name: Sürümü sunucuya gönder
env:
REL: ${{ github.run_id }}
run: |
test -d dist && [ -n "$(ls -A dist)" ] || { echo "build ciktisi bos, deploy durduruldu"; exit 1; }
STAMP=$(date -u +%Y%m%dT%H%M%S)
echo "STAMP=$STAMP" >> "$GITHUB_ENV"
rsync -az --delete --delete-after \
--exclude-from=deploy/rsync-exclude.txt \
--itemize-changes \
-e "ssh -o BatchMode=yes" \
dist/ deploy-target:"$STAMP"/
Üç ayrıntı: --delete-after silmeleri aktarım başarıyla bittikten sonra yapar, yani yarıda kopan bir bağlantı hedefi eksik bırakmaz; --itemize-changes logda hangi dosyanın neden değiştiğini gösterir ve beklenmedik bir silme listesi gördüğünüzde fark edersiniz; BatchMode=yes ise parola sorulacak bir duruma düşüldüğünde işi askıda bırakmak yerine hemen hata verir.
İlk kurulumda bir kez --dry-run ile çalıştırıp çıktıyı okuyun. rsync'in -a, sondaki eğik çizgi ve filtre kurallarının davranışı rsync ile dosya senkronizasyonu yazısında ayrıntılı; deploy bağlamında farkı, hatanın yedeklemede değil canlı veride ortaya çıkmasıdır.
Aynı Anda İki Deploy: concurrency ve Sunucu Tarafı Kilit#
İki push arka arkaya geldiğinde GitHub iki işi paralel başlatır. İkisi de aynı dizine rsync yapar ve dosyalar karışır; daha kötüsü, biri servisi yeniden başlatırken diğeri hâlâ dosya kopyalıyor olabilir.
Workflow seviyesinde sıraya sokun:
concurrency:
group: deploy-uretim
cancel-in-progress: false
cancel-in-progress değerinin false olması burada kritiktir. Test workflow'larında true mantıklıdır; deploy'da ise devam eden bir işi iptal etmek, sunucuyu yarım kopyalanmış dosyalarla baş başa bırakır. Yeni işin beklemesi doğru davranıştır.
Bu yine de yeterli değildir: elle tetiklenen bir workflow_dispatch, başka bir depo veya bir yönetici SSH oturumu aynı anda devreye girebilir. Aktivasyon betiğinin başına sunucu tarafı kilit koyun:
exec 9>/var/lock/uygulama-deploy.lock
flock -n 9 || { echo "baska bir deploy suruyor, iptal edildi" >&2; exit 75; }
flock -n beklemeden çıkar; çıkış kodu 75 (EX_TEMPFAIL) bunun geçici bir durum olduğunu belirtir ve workflow'da yeniden deneme mantığı kurmanızı kolaylaştırır. Dosya tanıtıcısı süreç bitince otomatik serbest kalır, temizlik kodu yazmanız gerekmez.
Sürümlü Dizin ve Symlink ile Saniyeler İçinde Geri Alma#
Tek dizine kopyalayan bir deploy, tanım gereği geri alınamaz: eski dosyaların üzerine yazmışsınızdır. Çözüm, her deploy'u ayrı bir dizine açıp yayına alma işini tek bir sembolik bağla yapmaktır.
/var/www/uygulama/
├── releases/
│ ├── 20260818T101500/
│ ├── 20260818T093000/
│ └── 20260817T221200/
├── shared/
│ ├── .env
│ └── uploads/
└── current -> releases/20260818T101500
shared/ içindeki kalıcı veriler hiçbir deploy'a ait değildir; her sürüm onlara sembolik bağla erişir. Böylece "yüklenen dosyaları silme" sorunu yapısal olarak ortadan kalkar.
Aktivasyon betiği:
#!/bin/bash
# /usr/local/bin/activate-release
set -euo pipefail
BASE=/var/www/uygulama
REL="$BASE/releases/$1"
exec 9>/var/lock/uygulama-deploy.lock
flock -n 9 || { echo "baska bir deploy suruyor" >&2; exit 75; }
[ -d "$REL" ] || { echo "surum dizini yok: $REL" >&2; exit 2; }
ln -s ../../shared/.env "$REL/.env"
ln -s ../../shared/uploads "$REL/public/uploads"
# Atomik geçiş: mv -T rename(2) çağırır, ara durum oluşmaz
ln -s "$REL" "$BASE/current.tmp"
mv -Tf "$BASE/current.tmp" "$BASE/current"
sudo /usr/bin/systemctl reload nginx
# Son 5 sürümü tut, gerisini sil
ls -1dt "$BASE"/releases/*/ | tail -n +6 | xargs -r rm -rf
İki satır özellikle önemli. mv -Tf yerine ln -sfn kullanmak yaygın bir kısayoldur ama ln, bağı önce silip sonra yeniden kurar; o mikrosaniyelik boşlukta gelen istek 404 alır. mv -T ise çekirdek düzeyinde rename(2) çağırdığı için ara durum yoktur. -T bayrağı ayrıca hedefi "dizin" değil "isim" olarak ele alır; onsuz mv yeni bağı current dizininin içine taşır ve yapı bozulur.
PHP-FPM kullanıyorsanız bir tuzak daha var. Nginx root yolunu symlink üzerinden çözer ve FPM'ye eski gerçek yolu bildirebilir; sonuç, yeni sürümü deploy ettiğiniz hâlde eski kodun çalışmasıdır. Çözüm $realpath_root kullanmaktır:
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
fastcgi_param DOCUMENT_ROOT $realpath_root;
}
Bu yalnızca doğru dosyayı çalıştırmakla kalmaz; her sürümün yolu farklı olduğu için OPcache anahtarları da farklılaşır ve bayat önbellek sorunu kendiliğinden çözülür.
Geri alma artık tek satırdır:
#!/bin/bash
# /usr/local/bin/rollback-release
set -euo pipefail
BASE=/var/www/uygulama
PREV=$(ls -1dt "$BASE"/releases/*/ | sed -n '2p')
[ -n "$PREV" ] || { echo "donulecek surum yok" >&2; exit 2; }
ln -s "${PREV%/}" "$BASE/current.tmp"
mv -Tf "$BASE/current.tmp" "$BASE/current"
sudo /usr/bin/systemctl reload nginx
echo "geri alindi: $PREV"
Node.js ya da Python gibi süreç tabanlı uygulamalarda reload nginx yerine uygulama servisini yeniden başlatmanız gerekir; PM2 ve systemd tarafının ayrıntısı Next.js uygulamasını sunucuya deploy etme yazısında.
Başarısız Deploy'u Otomatik Durdurmak: Sağlık Kontrolü#
Deploy'un "başarılı" sayılması için rsync'in hatasız bitmesi yeterli değildir. Uygulamanın gerçekten cevap verdiğini doğrulayın ve vermiyorsa aynı iş içinde geri alın.
- name: Sürümü yayına al
run: ssh deploy-target "release ${{ env.STAMP }}"
- name: Sağlık kontrolü
id: saglik
run: |
for i in $(seq 1 10); do
kod=$(curl -sS -o /dev/null -w '%{http_code}' --max-time 5 https://ornek.com/saglik || echo 000)
echo "deneme $i -> $kod"
[ "$kod" = "200" ] && exit 0
sleep 3
done
echo "saglik kontrolu basarisiz"
exit 1
- name: Başarısızsa geri al
if: failure() && steps.saglik.outcome == 'failure'
run: ssh deploy-target "rollback"
/saglik uç noktası sadece 200 OK dönen boş bir sayfa olmasın; veritabanı bağlantısını ve kritik bir bağımlılığı da yoklasın, aksi hâlde "web sunucusu ayakta ama uygulama ölü" durumunu kaçırırsınız.
Son olarak workflow'un tepesine şunu ekleyin:
defaults:
run:
shell: bash
Linux runner'larda varsayılan kabuk -e ile çalışır ama pipefail kapalıdır. shell: bash dediğinizde GitHub kabuğu -eo pipefail ile başlatır; böylece komut | tee log.txt gibi bir satırda ilk komut hata verse bile adımın yeşil kalması engellenir. Sessiz başarısızlıkların en sinsi kaynaklarından biri budur.
Sıkça Sorulan Sorular#
Deploy anahtarına passphrase koyabilir miyim?#
Teknik olarak evet, webfactory/ssh-agent gibi bir action ile passphrase'i ayrı bir secret'tan verebilirsiniz. Ancak pratik kazanç sınırlıdır: passphrase de aynı yerde, aynı korumayla saklanır. GitHub Secrets'ı okuyabilen bir saldırgan ikisini birden okur. Gerçek koruma, authorized_keys içindeki restrict ve command= kısıtlarıdır — anahtar sızsa bile shell açılmaz ve yalnızca tek bir dizine yazılabilir.
rsync yerine hazır bir deploy action kullansam olmaz mı?#
Olur; appleboy/ssh-action ve benzerleri çalışan araçlardır. Ancak üçüncü parti bir action sizin adınıza SSH oturumu açar, yani deponuza gelen her güncellemede sunucunuza erişebilecek yeni kod çalışır. Kullanacaksanız etiket yerine tam commit SHA'sına sabitleyin ve sürüm yükseltmelerini gözden geçirin. Doğrudan ssh ve rsync çağırmak birkaç satır daha uzundur ama zincirden bir halka çıkarır.
Deploy sırasında site kaç saniye kapalı kalır?#
Doğru kurulduğunda kapanmaz. Dosyalar yeni bir releases/ dizinine kopyalanırken eski sürüm çalışmaya devam eder; geçiş yalnızca mv -T ile symlink değiştiği anda olur ve bu işlem atomiktir. Kesinti riski, tek dizine doğrudan kopyalayan kurulumlarda ortaya çıkar: yarısı yeni yarısı eski dosyalarla çalışan uygulama beklenmedik hatalar üretir.
Veritabanı migration'larını nereye koymalıyım?#
Symlink geçişinden önce, yeni sürüm dizini hazır olduğunda çalıştırın; böylece kod yayına girdiğinde şema hazırdır. Ama geri alınabilirliği unutmayın: sütun silen ya da tip değiştiren bir migration, eski sürüme dönseniz bile geri gelmez. Kuralı ikiye bölün — önce yalnızca ekleyen migration'ları deploy edin, silme işlemlerini birkaç sürüm sonra ayrı bir deploy'da yapın.
Birden çok sunucuya deploy ederken ne değişir?#
Sunucu listesini bir matrix stratejisiyle dolaşabilirsiniz, ancak concurrency grubunu ortam bazında tutun ve sağlık kontrolünü her sunucu için ayrı çalıştırın. Kritik olan, bir sunucuda başarısızlık olduğunda diğerlerine devam etmemektir: fail-fast: true ile ilk hatada durun, sonra geri alma adımını tüm sunucularda çalıştırın. Aksi hâlde yarısı yeni yarısı eski sürüm çalışan bir küme kalır.
Staging ortamını aynı workflow ile yönetebilir miyim?#
Evet, ama ayrı environment, ayrı anahtar ve ayrı concurrency grubu kullanın. Aynı anahtarı iki ortamda paylaşmak, staging'de test edilen bir hatanın üretime sıçraması için gereken tek şeydir. GitHub'ın environment koruma kuralları burada işe yarar: staging otomatik geçsin, üretim için onay isteyin.