Cloudflare arkasındaki origin IP deploy subdomain'inden sızıyor — üç repo, on dört saniye, tek seferde düzeltildi
O öğleden sonra GitHub'a bir süre baktım. Üç repo PR'larını 16:20:31, 16:20:38 ve 16:20:45'te kapattı — on dört saniye arayla.
smarts #38: deploy.smarts.md → deploy.smartshow2claude #13: deploy.how2claude.com → deploy.how2claudepickful #118: deploy.pickful.ai / deploy.pickful.xyz → deploy.pickful / deploy.pickful-alphaÜçü de aynı sınıf bir bug'ı çözüyordu. Aslen Claude ile how2claude reposundaki ilgisiz küçük bir deploy sorunu hakkında konuşuyordum. Geçerken config/deploy.yml'a bir göz attı ve bana o makinenin origin IP'sinin açık DNS'de gözler önünde olduğunu söyledi.
Tüm projelerim Cloudflare'in arkasında. Cloudflare panelindeki o küçük turuncu bulut "kayıt proxylenmiş" anlamına gelir — HTTP istekleri önce bir Cloudflare edge node'una düşer, oradan benim origin'ime gider. Origin IP DNS yanıtlarında görünmez; DNS'in döndüğü Cloudflare anycast IP'sidir. O kısım doğruydu.
Ama Kamal ile deploy ediyorum. Kamal sunucuya SSH yaparak docker çalıştırıyor. SSH HTTP proxy'den geçemez, dolayısıyla Cloudflare tarafından proxylenmemiş bir hostname'e SSH yapmam gerekiyordu. O zamanki kurulumum:
# config/deploy.yml
servers:
web:
- deploy.how2claude.com # ← origin IP'ye doğrudan A kaydı, gri bulut (DNS only)
Cloudflare'de how2claude.com ve www.how2claude.com turuncuydu. deploy.how2claude.com griydi. Gri olmak zorundaydı, yoksa SSH makineye ulaşamazdı.
Gri açık DNS demektir. Herhangi biri şunu çalıştırabilir:
$ dig deploy.how2claude.com +short
<benim origin IP'm>
Ve deploy.<domain> gibi bir adlandırma kuralı başlı başına bir işarettir — yaygın SaaS domain'lerinin deploy.* subdomain'lerini taradığında, Cloudflare'in arkasında saklanması gereken origin IP'lerinden bir hasat alıyorsun.
Cloudflare WAF'ı atla, rate limiting'i atla, DDoS korumasını atla. Bir dig mesafesinde.
O gün başka bir konu konuşuyorduk. Yeni yazdığım bir deploy dokümanını gözden geçirmesini istedim; karşılaştırmak için config/deploy.yml'ı açtı, 7. satıra geldi — - deploy.how2claude.com — durakladı, sonra şuna benzer bir şey söyledi:
Bu hostname açık DNS üzerinden çözülüyor değil mi? Yani DNS sorgusu yapan herkes o origin IP'yi alabilir ve Cloudflare'in edge proxy'si baypas edilmiş olur.
Bir saniyeliğine donakaldım. Bunu fark etmem gerekirdi — Cloudflare'i konfigüre ederken o gri bulut ikonuna ikinci kez bakıp ne anlama geldiğini sormam yeterliydi — ama yapmadım. O hostname'i bir şekilde "iç" gibi muamele ediyordum, sırf prefix'i deploy. olduğu için. Beynim sessizce ona hiç olmamış bir gizlilik bahşediyordu.
Claude bu önyargıyı paylaşmıyor. Bir yaml alanındaki bir string okuyor ve mekanik soruyu soruyor: bu string nasıl IP'ye dönüşüyor? Cevap: açık DNS. Sonuç: origin IP açık.
/etc/hosts alias'ıÇözüm utanç verici derecede basit çıktı. Kamal hostname çözümlemesini lokal SSH istemcisine devrediyor, dolayısıyla hostname'in sadece benim makinemde çözülmesi yeterli — tüm internette çözülmesine gerek yok.
yaml'daki hostname'i kısalt:
servers:
web:
- deploy.how2claude # ← dikkat: .com yok
Sonra /etc/hosts'una bir satır ekle:
198.51.100.42 deploy.how2claude
Deploy yapan CI runner'larının da aynı satıra ihtiyacı var (env var, ya da workflow'da doğrudan echo >> /etc/hosts).
Sonuç:
deploy.how2claude.* kaydı yokkamal deploy / app exec / app logs komutları hala çalışıyor, çünkü /etc/hosts DNS'ten önce başvuruluyorVeritabanı accessory'si de aynı değişikliğe ihtiyaç duyar:
accessories:
db:
image: postgres:17
host: deploy.how2claude # ← aynı hostname
Yazmak istediğim kısım bu.
how2claude düzeltmesini ship ettikten sonra, başka bir bağlama geçecektim. Claude beni durdurdu: "smarts ve pickful için de Kamal kullanıyorsun, değil mi? Onlara da bakayım."
Baktı.
deploy.smarts.md — aynı sorun, açık A kaydıdeploy.pickful.ai (production), deploy.pickful.xyz (alpha), ayrıca uzun süredir ölü bir deploy-test1.pickful.ai ve emekli blockgeek.com domain'ine atıf yapan bir deploy.staging.ymlPickful üçü içinde en karmaşık olanıydı çünkü birden fazla destination'ı (production / alpha / test2) ve tarihi yükü vardı. Claude geçerken ölü staging ve test1 destination'larını da temizledi — artık kimse kullanmıyordu, sadece gürültüydü.
Son commit'ler:
smarts 6482472 2026-04-27 16:20:31 Use private deploy.smarts alias instead of public deploy.smarts.md (#38)
how2claude ccc0344 2026-04-27 16:20:38 Use private deploy.how2claude alias instead of public deploy.how2claude.com (#13)
pickful e6bf9af 2026-04-27 16:20:45 Deploy config hygiene + privatize hostnames (#118)
On dört saniye. Tabii bu sadece GitHub'ın merge kuyruğunu boşaltmasının zamanı — gerçek iş önceki iki saate yayılmıştı. Ama etkili biçim şuydu: bir bulgu, üç repo düzeltildi.
Tek başıma çalışsaydım, gerçekçi versiyon şöyle olurdu: how2claude'da düzelt, "smarts ve pickful için de yap" diyen bir TODO yaz, ve o TODO'nun listede üç ay yatmasını izle. Bu sırayı kendi gözlerimle birçok kez yaşadım.
Kamal, Capistrano veya SSH tabanlı herhangi bir deploy aracıyla deploy yapıyorsan:
config/deploy*.yml'ı aç ve host: ile servers: için grep yap. Görünen her hostname'i listele.dig +short çalıştır. Boş yerine origin IP döndüren her şey sızdırıyor.deploy.<project>) yeniden adlandır ve IP'yi /etc/hosts'a koy.echo "$IP deploy.<project>" | sudo tee -a /etc/hosts.Eğer ekibin aynı altyapı kalıbını paylaşan birden fazla projeye sahipse, tüm repoların deploy*.yml'lerini tarayan tek satırlık bir grep yaz; tek tek kontrol etmekten daha güvenilirdir.
Cloudflare başarısız olmadı. Kamal başarısız olmadı. Her iki araç da yapması gerekeni tam olarak yapıyordu.
Ders şu: varsayılan değerler seni sessizce yanıltır. deploy.<senin-domain>.com iç bir isim gibi geliyor, ama DNS'in "iç" diye bir kavramı yok — A kaydı A kaydıdır ve yayınlandığı an itibarıyla açıktır. Bir isim sana gizlilik yanılsaması verebilir ve Cloudflare panelindeki o küçük gri bulut ikonu aynı yanılsamanın görsel halidir: orada sessizce, kırmızı uyarı olmadan oturur, ama aslında sana "bu kayıt benden geçmiyor" der.
Claude'a deploy konfigürasyonunu bir kez okut. O bu yanılsamayı paylaşmıyor.