Bir zero downtime deploy, yeni sürümü yayına alırken kullanıcıların tek bir hata sayfası bile görmemesi demektir. Klasik yöntemde sunucuya bağlanır, dosyaları üzerine kopyalar, bağımlılıkları kurar ve servisi yeniden başlatırsınız; ama tam o saniyelerde site ya yarım dosyalarla çalışır ya da komple düşer. Bu rehberde, küçük ve orta ölçekli projelerde ek bir orkestrasyon aracına ihtiyaç duymadan, sadece symlink (sembolik bağlantı) ve bir health check ile kesintisiz geçişi nasıl kurduğunuzu adım adım anlatıyorum.
Sorun: "kopyala ve yeniden başlat" neden kesinti yaratır?
Dosyaları doğrudan canlı dizinin üzerine yazdığınızda iki tehlike vardır. Birincisi, kopyalama yarıda kaldığında web sunucusu eski ve yeni kodun karışımını servis eder; bu da PHP'de fatal error, varlık (asset) uyumsuzluğu veya yarım şablonlar demektir. İkincisi, composer install veya migration gibi adımlar saniyeler sürer ve bu süre boyunca uygulama tutarsız bir durumda kalır.
Çözümün özü basittir: yeni sürümü ayrı bir klasöre kurarsınız, her şey hazır olduğunda da canlı dizini gösteren bağlantıyı tek bir işlemle yeni klasöre çevirirsiniz. Linux'ta bir symlink'i değiştirmek atomik bir işlemdir; yani sunucu ya tamamen eski klasörü ya da tamamen yeni klasörü görür, asla ikisinin karışımını değil.
Dizin yapısı: releases, shared ve current
Capistrano ve Deployer gibi araçların yıllardır kullandığı, kanıtlanmış bir düzen var. Sunucuda şu yapıyı kurun:
/var/www/app/
├── releases/
│ ├── 2026-06-28-101500/
│ └── 2026-06-28-094200/
├── shared/
│ ├── .env
│ └── storage/
└── current -> releases/2026-06-28-101500/
Mantık şu: her deploy yeni bir zaman damgalı klasör olarak releases/ altına gelir. Sürümler arasında değişmeyen ve korunması gereken şeyler (ortam dosyası, yüklenen görseller, log'lar) shared/ altında durur ve her release'e symlink ile bağlanır. Web sunucusunun document root'u ise her zaman current bağlantısını gösterir. Geçiş, sadece bu current bağlantısını taşımaktan ibarettir.
Adım adım deploy akışı
Aşağıdaki sıra, hatasız bir geçişin iskeletidir. Her adımı bir önceki başarılı olduğunda çalıştırın:
- Yeni release klasörü oluştur ve kodu oraya çıkar (git clone, tar pipe veya CI artefaktı).
- Paylaşılan kaynakları bağla:
.envvestoragegibi öğelerishared/'dan yeni release'e symlink'le. - Bağımlılıkları kur:
composer install --no-dev -o, ardından gerekiyorsa frontend build. - Migration ve cache: şema değiştiyse
php artisan migrate --force, sonraphp artisan optimize. - Health check çalıştır: yeni release'in gerçekten ayağa kalktığını doğrula.
- Symlink'i çevir: sadece kontrol geçerse
current'ı yeni klasöre yönlendir.
Kritik nokta şu: symlink'i en sona bırakın. Build, bağımlılık kurulumu ve migration gibi yavaş işlemler henüz canlıya çıkmamış bir klasörde yapıldığı için kullanıcılar bunlardan hiç etkilenmez.
Atomik geçişin kalbi: symlink değiştirme
Bir symlink'i yerinde güncellemenin doğru yolu ln -sfn'dir. -f bayrağı mevcut bağlantının üzerine yazar, -n ise hedef bir dizinse bağlantının o dizinin içine girmesini engeller:
ln -sfn /var/www/app/releases/2026-06-28-101500 /var/www/app/current
Bu tek komut, bütün geçişin gerçekleştiği andır. Çoğu sistemde ln, hedefi önce geçici bir isimle oluşturup üzerine rename() çağrısıyla taşır ve rename() aynı dosya sisteminde atomiktir. Geçişten sonra PHP-FPM gibi süreçlerin OPcache'i hâlâ eski yolu önbelleğe almış olabilir; bu yüzden bağlantıyı çevirdikten sonra PHP-FPM'i nazikçe yeniden yükleyin (örneğin systemctl reload php8.3-fpm) ya da OPcache'i sıfırlayın.
Health check: yanlış sürümü canlıya almamak
Geçişin önündeki son bariyer health check'tir. Amaç, symlink'i çevirmeden önce yeni release'in gerçekten yanıt verdiğini doğrulamaktır. Uygulamanıza basit bir endpoint ekleyin (örneğin /health) ve deploy script'inde onu yoklayın:
URL="http://127.0.0.1/health"
for i in $(seq 1 10); do
code=$(curl -s -o /dev/null -w "%{http_code}" "$URL")
if [ "$code" = "200" ]; then
echo "Health OK"; exit 0
fi
sleep 2
done
echo "Health check basarisiz, deploy durduruldu"; exit 1
Script 200 dışında bir yanıt alırsa exit 1 ile durur ve current bağlantısı eski, çalışan sürümü göstermeye devam eder. Health endpoint'i yüzeysel olmamalı: yalnızca "PHP çalışıyor" demekle kalmayıp veritabanı bağlantısı ve kritik bir cache erişimi gibi bağımlılıkları da kontrol ederse, yarı bozuk bir sürümü canlıya alma riskini ortadan kaldırır.
Geri alma (rollback) ve eski sürümleri temizleme
Bu yapının en güzel yanı, geri almanın da deploy kadar hızlı olmasıdır. Bir sorun fark ederseniz current'ı bir önceki release'e geri çevirmek yeterlidir:
ln -sfn /var/www/app/releases/2026-06-28-094200 /var/www/app/current
systemctl reload php8.3-fpm
Eski release'ler diskte birikmesin diye, en yeni 3-5 tanesini tutup gerisini silen küçük bir temizlik adımı ekleyin. Böylece hem geri alma için yedeğiniz olur hem de disk dolmaz.
Migration'larda dikkat: geriye uyumlu değişiklikler
Symlink atomik olsa da veritabanı şeması öyle değildir. Eski ve yeni kod, geçiş anında aynı tabloyu kısa süre paylaşır. Bu yüzden yıkıcı şema değişikliklerini (kolon silme, yeniden adlandırma) iki aşamaya bölün: önce yeni yapıyı ekleyin ve iki sürümün de çalışacağı şekilde deploy edin; eski kod tamamen devre dışı kaldıktan sonra, bir sonraki deploy'da eski kolonları temizleyin. Bu "expand and contract" yaklaşımı, gerçek kesintisizliğin gizli yarısıdır.
Sık Sorulan Sorular
Bunun için Docker veya Kubernetes şart mı?
Hayır. Symlink tabanlı deploy, tek bir VPS veya paylaşımlı hosting üzerinde, hiçbir konteyner olmadan mükemmel çalışır. Docker ve Kubernetes ölçek büyüdüğünde ve birden çok sunucu/replikayı yönetmeniz gerektiğinde değer katar; ama küçük-orta projeler için symlink yaklaşımı çok daha basit ve yeterlidir.
Health check için ayrı bir endpoint şart mı?
Ana sayfayı yoklamak da iş görür, ama özel bir /health endpoint'i tercih edilir. Bağımlılıkları (DB, cache) bilinçli olarak kontrol edebilir, hafif tutulabilir ve log'larda gürültü yaratmadan izlenebilir.
Symlink çevirdikten sonra neden PHP-FPM'i yeniden yüklemek gerekiyor?
OPcache, dosyaları çözümlenmiş gerçek yollarıyla önbelleğe alabilir. Bağlantıyı çevirseniz bile süreçler eski koddan servis vermeye devam edebilir. reload (restart değil) bu önbelleği tazeler ve bunu kesinti yaratmadan yapar.
Kesintisiz bir deploy hattı kurmak mı istiyorsunuz? İster Laravel ister başka bir yığın olsun, mevcut sunucunuza symlink tabanlı, health check'li ve tek komutla geri alınabilen bir deploy akışı kurmanıza yardımcı olabilirim. Benimle iletişime geçin ve projenizi konuşalım.