scp ile .env dosyasını sunucuya elle taşımak konfigürasyon yönetimi değil. Hangi değerin şu an production’da çalıştığını kimse bilmiyor, dosya hiçbir yerde versiyonlanmıyor, sunucu silinince dosya da onunla gidiyor.
Elle kopyalamanın kırdığı şey
Üç sunucu varsa üç ayrı .env dosyası var demektir. Biri güncellenip diğer ikisi unutulunca hangi sunucunun hangi değeri kullandığı bir tahmin işine dönüşür. Bir değişkeni değiştirmek için sunucuya bağlanıp dosyayı elle düzenlemek gerekiyorsa, o değer aslında yönetilmiyor demektir.
Git geçmişi yok, diff yok, “kim ne zaman değiştirdi” sorusuna cevap yok. Sızıntı olduğunda ilk soru “bu değer ne zamandır böyleydi” oluyor ve kimse bilmiyor.
Platform değişkeni enjekte etsin
Değeri platforma bırakın. Coolify, Docker Compose ya da systemd, hepsi ortam değişkenini süreç başlarken enjekte edebiliyor.
Coolify’da bu, uygulamanın Environment Variables sekmesinde tutuluyor ve container her başladığında ortama yazılıyor. Bu blogu Coolify’da nasıl kurduğumu bu blogu neden ve nasıl kurdum yazısında anlattım. Docker Compose’da environment anahtarı aynı işi görüyor:
services:
app:
image: registrydeki-imaj
environment:
APP_KEY: ${APP_KEY}
DB_PASSWORD: ${DB_PASSWORD}
systemd’de aynı şey EnvironmentFile ile yapılıyor:
[Service]
EnvironmentFile=/etc/myapp/app.env
ExecStart=/usr/bin/myapp
Fark şu: değer artık dosya sistemine elle scp’lenen bir şey değil, platformun bir parçası. Yeni bir sunucuya taşınırken kopyalanacak dosya değil, girilecek bir arayüz var.
.env.example her key’i listeler, değer içermez
Commit’lenen tek dosya .env.example. İçinde her key var, değer yok:
APP_KEY=
DB_PASSWORD=
STRIPE_SECRET=
Yeni bir geliştirici ya da yeni bir sunucu kurulumu bu dosyayı okuyup hangi değişkenlerin gerekli olduğunu görüyor, tahmin etmiyor.
Eksik key’de sessizce varsayılana düşmeyin
Başlangıçta zorunlu değişkenleri kontrol edin, eksikse süreç başlamasın:
foreach (['APP_KEY', 'DB_PASSWORD', 'STRIPE_SECRET'] as $key) {
if (empty(env($key))) {
throw new RuntimeException("Eksik ortam değişkeni: {$key}");
}
}
Eksik key’de null ya da boş string’e düşen kod, yanlış yapılandırmayı prod’da sessizce çalıştırır. Hata üç hafta sonra, farklı bir belirtiyle çıkar.
Build-time ile runtime’ı karıştırmayın
Bir statik sitede bir değer build sırasında HTML’e gömülüyorsa, o değeri değiştirip container’ı yeniden başlatmak hiçbir şeyi değiştirmez. Değer zaten dosyaya yazılmış durumda. Gerekli olan yeniden başlatma değil, yeniden build. Bu ayrım Docker imajınız neden 1,2 GB yazısındaki build/runtime katman ayrımıyla aynı yerden geliyor.
“Değeri değiştirdim ama hiçbir şey olmadı” şikayetinin sebebi neredeyse hep bu ayrımı atlamak. Runtime değişkeni, süreç her başladığında okunan DB_PASSWORD gibi bir değer, restart ister. Build-time değişkeni, bundle’a gömülen değer, rebuild ister.
CI’da secret nasıl geçer
GitHub Actions gibi bir CI kullanıyorsanız secret’lar repo ayarlarında tutulur, workflow dosyasına asla değer olarak yazılmaz:
jobs:
deploy:
steps:
- name: Build
env:
APP_KEY: ${{ secrets.APP_KEY }}
run: pnpm build
CI, secret’ı build adımına ortam değişkeni olarak enjekte ediyor ve log çıktısında değeri otomatik maskeliyor. Bu da aynı prensibin bir örneği: değer bir dosyada değil, platformun secret deposunda duruyor.
Sızan değeri commit’ten silmek yetmiyor
Gerçek .env asla commit’lenmez. .gitignore’a girer, girmemişse şimdi girer.
Zaten commit’lenmişse geçmişten silmek yeterli değil. Git geçmişi rewrite edilse bile fork’larda, clone’larda ve CI loglarında kopyası kalmış olabilir. Sızan her token ve şifre rotate edilir, silinen commit sızıntıyı geri almıyor. Rotate işlemi fark edildiği gün yapılır; “hafta sonuna bırakalım” diye ertelenen bir rotate, ertelendiği kadar açık bir pencere demektir.
Özet
Değeri platforma enjekte ettirin, .env.example’ı commit’leyin, eksik key’de süreç patlasın. Build-time değişkeni değiştirdiyseniz restart değil rebuild yapın. Sızan her değeri rotate edin, commit’i silmek yetmez.