ה-IP של שרת המקור שמאחורי Cloudflare דולף דרך תת-הדומיין deploy — שלושה ריפוזיטוריז ב-14 שניות, תוקנו במכה אחת
באותו אחר-צהריים בהיתי בגיטהאב לרגע. שלושה ריפוזיטוריז סגרו PR-ים ב-16:20:31, 16:20:38 וב-16:20:45 — בהפרש של ארבע-עשרה שניות.
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שלושת התיקונים היו מאותה משפחת באגים. במקור פשוט שוחחתי עם Claude על תקלת deploy קטנה לא קשורה ב-how2claude. כבדרך אגב הוא העיף מבט ב-config/deploy.yml ואמר לי שה-IP של שרת המקור של אותה מכונה חשוף לעין כל ב-DNS הציבורי.
כל הפרויקטים שלי יושבים מאחורי Cloudflare. הענן הקטן הכתום בלוח הבקרה של Cloudflare משמעו "רשומה ב-proxy" — בקשות HTTP נוחתות קודם בצומת edge של Cloudflare ומשם לשרת המקור שלי. ה-IP של שרת המקור לא מופיע בתשובות DNS; מה שה-DNS מחזיר הוא IP של anycast של Cloudflare. החלק הזה היה בסדר.
אבל אני עושה deploy עם Kamal. Kamal מבצע SSH לשרת כדי להריץ docker. SSH לא יכול לעבור דרך HTTP proxy, ולכן הייתי צריך hostname שאינו בפרוקסי של Cloudflare כדי לגשת אליו ב-SSH. ההגדרה שלי הייתה:
# config/deploy.yml
servers:
web:
- deploy.how2claude.com # ← רשומת A ישירות ל-IP של שרת המקור, ענן אפור (DNS only)
ב-Cloudflare, how2claude.com ו-www.how2claude.com היו כתומים. deploy.how2claude.com היה אפור. הוא חייב היה להיות אפור, אחרת SSH לא היה מגיע למכונה.
אפור פירושו DNS ציבורי. כל אחד יכול להריץ:
$ dig deploy.how2claude.com +short
<ה-IP של שרת המקור שלי>
ומוסכמת השמות deploy.<domain> היא כשלעצמה רמז ברור — סרוק את תת-הדומיין deploy.* על-פני רשימת דומיינים נפוצים של SaaS, ויש לך קציר של כתובות IP של שרתי מקור שאמורים להסתתר מאחורי Cloudflare.
עוקפים את ה-WAF של Cloudflare, עוקפים rate limiting, עוקפים הגנת DDoS. במרחק dig אחד.
באותו רגע דיברנו על משהו אחר. ביקשתי ממנו לסקור מסמך deploy שזה עתה כתבתי; הוא פתח את config/deploy.yml להצלבה, הגיע לשורה 7 — - deploy.how2claude.com — עצר לרגע, ואמר משהו בסגנון:
ה-hostname הזה נפתר דרך DNS ציבורי, נכון? כלומר שכל מי שמריץ שאילתת DNS מקבל את ה-IP של שרת המקור, וה-edge proxy של Cloudflare נעקף.
קפאתי לרגע. את זה הייתי צריך לראות מזמן — היה מספיק להעיף עוד מבט באייקון של הענן האפור בזמן ההגדרה של Cloudflare ולשאול את עצמי מה זה אומר — אבל לא עשיתי זאת. התייחסתי ל-hostname הזה כאל משהו "פנימי", אך ורק משום שהקידומת הייתה deploy.. המוח שלי העניק לו בשקט פרטיות שמעולם לא הייתה לו.
ל-Claude אין את ההטיה הזאת. הוא קורא מחרוזת בשדה yaml ושואל את השאלה המכנית: איך המחרוזת הזאת הופכת ל-IP? תשובה: DNS ציבורי. מסקנה: ה-IP של שרת המקור הוא ציבורי.
/etc/hostsהתיקון הסתבר כפשוט עד מבייש. Kamal מבקש מ-SSH client מקומי לפתור את ה-hostname, כך שה-hostname צריך להיפתר רק על המכונה שלי — לא בכל האינטרנט.
קצרו את ה-hostname ב-yaml:
servers:
web:
- deploy.how2claude # ← לב: בלי .com
ואז תוסיפו שורה ל-/etc/hosts שלי:
198.51.100.42 deploy.how2claude
ל-runners של CI שעושים deploy יש צורך באותה שורה (משתנה סביבה, או echo >> /etc/hosts ישירות ב-workflow).
תוצאה:
deploy.how2claude.*kamal deploy / app exec / app logs ממשיכות לעבוד, כי /etc/hosts נבדק לפני DNSהאקססורי של מסד הנתונים זקוק לאותו שינוי:
accessories:
db:
image: postgres:17
host: deploy.how2claude # ← אותו hostname
זה החלק שרציתי להעלות על הכתב.
אחרי שהשטנו את התיקון של how2claude, עמדתי לעבור הקשר. Claude עצר אותי: "אתה משתמש ב-Kamal גם ב-smarts וב-pickful, נכון? תן לי לבדוק גם את אלה."
הוא בדק.
deploy.smarts.md — אותה בעיה, רשומת A ציבוריתdeploy.pickful.ai (production), deploy.pickful.xyz (alpha), בנוסף deploy-test1.pickful.ai שמת מזמן ו-deploy.staging.yml שהפנה לדומיין שיצא משימוש, blockgeek.compickful היה המסובך מבין השלושה כי היו לו כמה destinations (production / alpha / test2) ומשקעים היסטוריים. תוך כדי, Claude טאטא את destinations המתות staging ו-test1 — ממילא כבר אף אחד לא השתמש בהן, רק רעש.
ה-commits הסופיים:
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)
ארבע-עשרה שניות. כמובן, זה רק GitHub שרוקן את תור ה-merge — העבודה האמיתית התפזרה על שעתיים שלפני. אבל הצורה האפקטיבית הייתה: ממצא אחד, שלושה ריפוזיטוריז מתוקנים.
אם הייתי עובד לבד, הגרסה הריאליסטית היא: לתקן ב-how2claude, לרשום TODO "לעשות גם smarts ו-pickful", ולצפות ב-TODO הזה שוכב ברשימה שלושה חודשים. ראיתי את הרצף הזה בדיוק פעמים רבות.
אם אתה עושה deploy עם Kamal, Capistrano או כל כלי deploy מבוסס SSH:
config/deploy*.yml ועשו grep על host: ועל servers:. רשמו כל hostname שמופיע.dig +short על כל אחד. כל מה שמחזיר IP של שרת מקור במקום כלום מדליף.deploy.<project>) ושימו את ה-IP ב-/etc/hosts.echo "$IP deploy.<project>" | sudo tee -a /etc/hosts.אם הצוות שלכם מנהל כמה פרויקטים שחולקים את אותו תבנית תשתית, כתבו grep בשורה אחת שיסרוק את כל ה-deploy*.yml בכל הריפוזיטוריז — זה אמין יותר מאשר לבדוק אחד-אחד.
לא Cloudflare כשל ולא Kamal כשל — שני הכלים עשו בדיוק את מה שהיו אמורים לעשות.
הלקח הוא: ערכי ברירת מחדל מטעים אותך בשקט. deploy.<your-domain>.com נשמע כשם פנימי, אבל ל-DNS אין מושג של "פנימי" — רשומת A היא רשומת A, ומרגע שהיא פורסמה היא ציבורית. שם יכול להעניק לך אשליה של פרטיות, ואותו אייקון קטן של ענן אפור בלוח הבקרה של Cloudflare הוא רק הצורה הוויזואלית של אותה אשליה: יושב לו שם בשקט, בלי אזהרות אדומות, אבל במציאות אומר "הרשומה הזאת לא עוברת דרכי".
תנו ל-Claude לקרוא פעם אחת את הקונפיג של ה-deploy שלכם. הוא לא חולק את האשליה.