SSD Nodes Learn Hosting plans →
מדריכים Matt Connorמאת Matt Connor · עודכן 2026-10-21

איך עובדת רשת ב-Docker Compose: מדריך מעשי

למדו איך Docker Compose מנהל רשתות, החל מ-bridge פנימי ו-DNS לפי שם שירות ועד לשיתוף רשתות בין פרויקטים. נסביר מתי להשתמש ב-host mode ואיך פתיחת פורטים עוקפת את UFW.

מה Docker Compose בונה לפני שהיישום שלך עולה

הרשת ב-Docker Compose מתחילה בכלל אחד: docker compose up יוצר רשת פרטית עבור הפרויקט, מחבר אליה כל שירות, ומאפשר לשירותים אלו להגיע זה לזה לפי שם השירות. אין צורך לכתוב שורת networks: אחת כדי להשיג זאת. רוב הבלבול סביב הרשת ב-Compose נובע מחוסר ידיעה שברירת המחדל הזו כבר קיימת.

להלן קובץ קטן. שמור אותו כ-compose.yaml בתוך תיקייה בשם shop.

services:
  web:
    image: nginx:1.27
    ports:
      - "8080:80"
  db:
    image: postgres:16
    environment:
      POSTGRES_PASSWORD: example

העלה את השירותים ובדוק מה Docker יצר:

docker compose up -d
docker network ls

הרשימה מכילה כעת רשת בשם shop_default. Compose מעניק לה את השם <project>_default, ושם הפרויקט כברירת מחדל הוא שם התיקייה באותיות קטנות. ניתן לדרוס זאת באמצעות docker compose -p myproject up -d או באמצעות הגדרת name: myproject ברמה העליונה בקובץ. ה-driver שלה הוא bridge, שהוא מתג וירטואלי בתוך המארח. כל מכולה מקבלת כתובת ברשת משנה פרטית, ותעבורה יוצאת מתורגמת לכתובת של המארח בדרכה החוצה.

הפקודה docker compose down מוחקת את הרשת הזו שוב. זו הסיבה שמכולה "יתומה" מפרויקט ישן עלולה להשאיר רשת פתוחה: Docker יסרב למחוק אותה עם השגיאה error while removing network: network shop_default has active endpoints, והפתרון הוא לעצור או להסיר את המכולה שעדיין מחוברת אליה.

אם Compose חדש לך, כדאי לקרוא תחילה את מבנה קובץ ה-Compose ופקודות מחזור החיים, כיוון שכל מה שמופיע להלן מניח שביכולתך להפעיל ולעצור פרויקט.

פתרון שמות (DNS) לפי שם שירות הוא החלק שמתחילים מפספסים

בכל רשת שמוגדרת על ידי המשתמש, Docker מריץ שרת DNS פנימי שכל מכולה רואה בכתובת 127.0.0.11. הוא מתרגם שמות שירותים לכתובות ה-IP של המכולות הנוכחיות. לכן, web מגיע למסד הנתונים בשם המארח db, בפורט 5432, ללא צורך בהגדרה כלשהי.

docker compose exec web getent hosts db

פקודה זו מדפיסה שורה בסגנון 172.18.0.2 db. אם היא לא מדפיסה דבר, שני השירותים אינם נמצאים על אותה רשת.

הטעות שכמעט כולם עושים פעם אחת היא שימוש ב-localhost בהגדרות היישום. בתוך מכולה, localhost הוא המכולה עצמה, לא המארח ולא השירות האחר. לקוחות Postgres מדווחים על כך בבירור:

could not connect to server: Connection refused
	Is the server running on host "localhost" (127.0.0.1) and accepting
	TCP connections on port 5432?

מחרוזת החיבור צריכה להיות postgresql://postgres:example@db:5432/postgres. חלק ה-host הוא שם השירות.

שני פרטים שחוסכים זמן בהמשך: שמות מתורגמים למה שרץ כרגע, לכן docker compose up -d --scale web=3 נותן שם אחד עם שלוש כתובות, ולקוח שמבצע שמירה במטמון (caching) של DNS לנצח ינעל את עצמו על מכולה שכבר אינה פעילה. בנוסף, רשת ה-bridge המיושנת שבה משתמשים ב-docker run פשוט ללא --network אינה יכולה לתרגם שמות מכולות כלל: מכולות שם מוצאות זו את זו רק לפי כתובת IP, והן מחפשות שמות מארח חיצוניים דרך עותק של קובץ ה-/etc/resolv.conf של המארח. זו הסיבה שעצות לגבי קישורי מכולות (container links) משנת 2016 אינן תואמות למה שאתם רואים כיום.

אין צורך ב-ports: כדי לחבר בין שני שירותים

הפקודה ports: מפרסמת פורט של מכולה אל המארח. היא מיועדת לתעבורה המגיעה מחוץ ל-Docker. אין לה שום קשר לתעבורה בין שירותים, שעובדת ממילא על פני כל טווח הפורטים ברשת הפרויקט.

לכן, ה-ports: - "5432:5432" שרבים מוסיפים לשירות מסד הנתונים שלהם אינו מועיל ואף מזיק: הוא חושף את Postgres לממשק הציבורי של השרת. מחקו אותו. אם ברצונכם לגשת אליו מהמחשב הנייד לצורך ביצוע הגירה (migration), בצעו bind ל-loopback באמצעות "127.0.0.1:5432:5432" וגשו אליו דרך מנהרת SSH. ההבדל בין socket מאזין, פורט מפורסם וחוק firewall מוסבר ב-איך פורטים ושירותים מאזינים עובדים ב-Linux.

הפקודה expose: היא תיעודית בלבד ב-Compose. היא אינה פותחת דבר, כיוון ששום דבר לא היה סגור בין מכולות הנמצאות על אותה רשת.

מתי network_mode host הוא הבחירה הנכונה, ומה המחיר

מצב host מבטל את מרחב השמות (namespace) של הרשת של המכולה ומאפשר לתהליך להשתמש בממשקי הרשת של המארח ישירות.

services:
  probe:
    image: alpine:3.20
    network_mode: host
    command: sleep infinity

יש סיבות מוצדקות לרצות זאת. תהליך שצריך לראות תעבורת broadcast או multicast ברשת המקומית, כמו גילוי התקנים עבור שרת מדיה או רכזת בית חכם, לא יכול לראות זאת מבעד ל-bridge, כיוון שה-bridge אינו מעביר תעבורה כזו למכולה. סוכן ניטור שקורא את מוני הממשקים של המארח זקוק לממשקים של המארח. בנוסף, אתם מדלגים על שלב תרגום הכתובות, מה שחשוב בקצבי חבילות (packets) גבוהים.

המחירים הם ספציפיים.

ports: מפסיק לעבוד. Docker מזהיר שפורטים מפורסמים (published) מושלכים בעת שימוש במצב רשת host, והמכולה מאזינה לכל מה שהתהליך שלה מאזין לו. שתי מכולות במצב host שמנסות להשתמש בפורט 8080 יתנגשו, והשנייה תקרוס עם bind: address already in use.

פתרון שמות לפי שם שירות נעלם בשני הכיוונים. המכולה אינה נמצאת ברשת הפרויקט, לכן היא לא יכולה לתרגם את db, ושירותים אחרים לא יכולים לתרגם אותה. היא עדיין יכולה להגיע אליהם דרך פורטים שפורסמו במארח, בדרך כלל ב-127.0.0.1. בלינוקס היא יכולה גם להגיע למכולת bridge ישירות בכתובת ה-IP של אותה מכולה בכל פורט מאזין, כיוון שלמארח יש גישה פתוחה למכולות ברשתות ה-bridge שלו, ומכולה במצב host חולקת את מחסנית הרשת של המארח. כתובות אלו משתנות כאשר מכולות נוצרות מחדש, לכן פורט שפורסם ב-loopback הוא הנתיב היציב.

הבידוד נעלם. תהליך שמאזין ל-0.0.0.0 בתוך מכולה במצב host מאזין לכל ממשק בשרת שלכם, כולל הממשק הציבורי, בדיוק כמו חבילה שהותקנה עם apt. יש יתרון אחד לכך: תעבורה זו עוברת בנתיב הקלט הרגיל, לכן חוקי UFW אכן חלים עליה, מה שלא נכון לגבי פורטים מפורסמים.

מצב host הוא תכונה של Docker Engine בלינוקס. Docker Desktop תומך בו רק מגרסה 4.34 ואילך ורק לאחר הפעלתו, עם מגבלות נוספות לפיהן מכולות אינן יכולות להאזין לכתובות IP של המארח ורק TCP ו-UDP נתמכים. אם חצי מהצוות שלכם עובד על שרתי לינוקס וחצי על Docker Desktop, צפו לכך שאותו קובץ יתנהג אחרת.

השתמשו במצב host רק כשאתם זקוקים לממשקים של המארח. אל תשתמשו בו כדי לפתור בעיית חיבור, כי הוא בדרך כלל מחליף בעיה אחת בבעיה קשה יותר.

חיבור בין שני פרויקטי Compose באמצעות רשת חיצונית

רשת שנוצרה על ידי פרויקט אחד אינה גלויה לאחר. זו הסיבה ש־reverse proxy ב־proxy/compose.yaml אינו יכול לראות יישום ב־app/compose.yaml, גם אם הם נמצאים על אותו שרת. הפתרון הוא יצירת רשת שאינה שייכת לאף אחד מהפרויקטים.

צרו אותה פעם אחת, באופן ידני:

docker network create edge

לאחר מכן, הגדירו אותה כחיצונית (external) בכל פרויקט. בצד ה־proxy:

services:
  proxy:
    image: traefik:v3.1
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
    networks:
      - edge

networks:
  edge:
    name: edge
    external: true

בצד היישום:

services:
  app:
    image: nginx:1.27
    networks:
      - edge
      - internal
  db:
    image: postgres:16
    environment:
      POSTGRES_PASSWORD: example
    networks:
      - internal

networks:
  edge:
    name: edge
    external: true
  internal:

הערך external: true מורה ל־Compose להתחבר לרשת קיימת במקום ליצור אחת חדשה, ולהשאיר אותה פעילה לאחר docker compose down. המפתח הנפרד name: חשוב יותר ממה שנראה במבט ראשון: בלעדיו, Compose מחפש רשת ששמה זהה בדיוק ל־edge, ואילו איתו ניתן להשתמש בשם אחד בתוך הקובץ ובשם אחר במערכת המארחת.

אם הרשת אינה קיימת, Compose יסרב לעלות וידווח שהרשת הוגדרה כחיצונית אך לא נמצאה. יש ליצור אותה תחילה.

שימו לב למה שקובץ היישום עושה עם internal. מסד הנתונים נמצא רק ברשת המקומית של אותו פרויקט, לכן ה־proxy אינו יכול להגיע אליו ורק app יכול. הוספת internal: true תחת הגדרת רשת מרחיקה לכת ומסירה את הניתוב שלה לעולם החיצון לחלוטין. זוהי ברירת מחדל טובה עבור מסד נתונים, עם עלות אחת שכדאי להכיר לפני ההגדרה: מכולה ברשת פנימית אינה יכולה להוריד דבר, לכן תהליך entrypoint שמריץ apt-get update או pip install בעת העלייה ייתקע ולבסוף ייכשל עקב timeout.

להגדרה מלאה הכוללת חוקי ניתוב ותעודות, ראו הרצת מספר יישומים מאחורי מופע Traefik יחיד.

פורטים שפורסמו עוקפים את UFW

זהו החלק ברשתות של Docker Compose שהופך לתקרית אבטחה. אתם מפרסמים פורט, מוודאים ש־UFW פעיל וחוסם הכל מלבד SSH, ובכל זאת השירות נגיש מהאינטרנט.

sudo ufw status
curl http://203.0.113.10:8080

UFW מדווח שהפורט חסום. ה־curl מחזיר את הדף בכל זאת. שום דבר לא מקולקל. Docker כותב את חוקי הניתוב והעברת הכתובות שלו ישירות לתוך iptables, ותעבורה לפורט של מכולה שפורסם מועברת למכולה במקום להימסר למארח, לכן היא לעולם לא עוברת בשרשרת ש־UFW מנהל עבור תעבורה המיועדת למארח עצמו. החוקים של Docker גם מקבלים עדיפות על אלו של UFW.

הפתרון המהיר הוא לפרסם רק היכן שצריך:

    ports:
      - "127.0.0.1:8080:80"

זה קושר את צד המארח ל־loopback, כך שהפורט נגיש מהשרת עצמו ודרך מנהרת SSH, ולא משום מקום אחר. הציבו את נקודת הכניסה הציבורית מאחורי reverse proxy שמפרסם את 80 ו־443 באופן מכוון. ההסבר המלא, כולל שרשרת ה־DOCKER-USER למקרים שבהם אתם חייבים לסנן פורט שפורסם, נמצא ב־מדוע Docker מפרסם פורטים תוך עקיפת UFW וכיצד לתקן זאת.

איך לנפות שגיאות בארבע פקודות

התחילו בשאלה באיזו רשת נמצאת כל מכולה בפועל:

docker network inspect shop_default

בלוק ה-Containers מפרט כל מכולה מחוברת עם הכתובת שלה. שירות שחסר ברשימה זו נמצא ברשת אחרת, במצב host, או שאינו רץ.

בדקו פתרון שמות ממכולה זמנית שמחוברת לאותה רשת, כך שלא תצטרכו שום כלי עזר בתוך האימג'ים שלכם:

docker run --rm --network shop_default busybox nslookup db
docker run --rm --network shop_default busybox nc -zv db 5432

כשל ב-nslookup מצביע על בעיה בפתרון שמות או בחברות ברשת. אם nslookup מצליח בעוד nc נכשל, סימן שהשם והרשת תקינים והבעיה היא בחיבור עצמו. אם nc מדווח על Connection refused, הסיבה הרגילה היא שהשירות אינו מאזין בפורט זה, או שהוא מאזין ב-127.0.0.1 בתוך המכולה שלו במקום ב-0.0.0.0. המקרה האחרון נפוץ בשרתי פיתוח, והתיקון נמצא בכתובת ה-bind של היישום, לא ב-Docker. אם nc נתקע ומגיע ל-timeout, חפשו משהו שחוסם את התעבורה בדרך, כמו חוק firewall, או מכולה שהושהתה או אינה מגיבה.

כשל נוסף שנראה כמו באג ב-Docker: אם מכולות יכולות לתקשר זו עם זו אך לא יכולות להגיע למכונה ברשת המשרדית או ב-VPN שלכם, כנראה שרשת המשנה (subnet) של Docker חופפת לרשת ההיא. כברירת מחדל, Docker מקצה כתובות מ-172.17.0.0/16 ומעלה. העבירו את המאגר ב-/etc/docker/daemon.json:

{
  "default-address-pools": [
    { "base": "10.200.0.0/16", "size": 24 }
  ]
}

לאחר מכן הריצו את sudo systemctl restart docker וצרו מחדש את הרשתות המושפעות, כיוון שרשת קיימת שומרת על רשת המשנה שבה נוצרה.

FAQ

מדוע המכולות שלי לא מצליחות לתקשר זו עם זו לפי שם השירות?

הן אינן נמצאות על אותה רשת. Compose מצרף כל שירות לרשת <project>_default באופן אוטומטי, אך ברגע שמוסיפים רשימת networks: לשירות, רשימה זו הופכת להגדרה המלאה של הרשתות עבורו והרשת המשתמעת אינה מתווספת עוד. הריצו את docker network inspect <network> וודאו ששתי המכולות מופיעות בבלוק ה-Containers. בדקו גם שאף שירות לא משתמש ב-network_mode: host, כיוון שמכולה במצב host אינה נמצאת על אף רשת Docker ואינה יכולה לבצע רזולוציה לשמות שירותים.

האם עליי לחשוף פורטים כדי ששירות אחד יגיע לאחר?

לא. ברשת Compose, כל פורט של כל מכולה נגיש לשאר המכולות באותה רשת. ports: קיים רק כדי לחשוף מכולה לתעבורה מחוץ ל-Docker, ו-expose: הוא תיעודי בלבד. חשיפת פורט של מסד נתונים היא הרגל נפוץ ויקר, שכן היא מציבה את מסד הנתונים על ממשק הרשת הציבורי של השרת.

מה ההבדל בין רשת bridge לרשת host?

מצב bridge מעניק למכולה מרחב רשת (network namespace) וכתובת משלה על מתג וירטואלי, עם רזולוציית שמות אוטומטית בין מכולות ותרגום תעבורה יוצאת. מצב host מעניק למכולה גישה ישירה למחסנית הרשת של המארח: ללא כתובת נפרדת, ללא רזולוציה לפי שם שירות, ללא חשיפת פורטים וללא בידוד ממאזינים אחרים על המארח. מצב bridge הוא ברירת המחדל והבחירה הנכונה, אלא אם התהליך מחייב גישה לממשקי הרשת של המארח.

כיצד אחבר מכולות משני קובצי Compose שונים?

צרו רשת משותפת באמצעות docker network create edge, לאחר מכן הגדירו אותה בשני הקבצים תחת external: true וחברו את השירותים שצריכים לתקשר. Compose לא ייצור ולא ימחק רשת זו. אם תדלגו על שלב היצירה, Compose יסרב לעלות וידווח שהרשת הוגדרה כחיצונית (external) אך לא נמצאה.

מדוע המכולה שלי נגישה מהאינטרנט למרות ש-UFW חוסם את הפורט?

מכיוון שפורט חשוף מנוהל על ידי חוקי ה-forwarding ש-Docker מוסיף ל-iptables. חוקים אלו נבדקים לפני החוקים של UFW, ותעבורה שעוברת ניתוב (forwarded) אינה עוברת בשרשרת הסינון של UFW ממילא. בצעו Bind לצד המארח ל-loopback באמצעות "127.0.0.1:8080:80" והציבו כל שירות ציבורי מאחורי reverse proxy בפורטים 80 ו-443.

האם המאמר הזה היה מועיל?