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

Docker Compose: בחירה בין bind mount ל-named volume

מה ההבדל בין bind mount ל-named volume ב-Docker Compose? מדריך מעשי לבחירת סוג האחסון הנכון עבור קבצי הגדרות או בסיסי נתונים, כולל פתרון שגיאת volume not found וניהול הרשאות.

Bind mount או named volume: התשובה הקצרה

ב-Docker Compose קיימים שני סוגים של volumes, והבחירה ביניהם תלויה בשאלה מי הבעלים של הקבצים. השתמשו ב-bind mount עבור קבצים שאתם קוראים ועורכים בעצמכם, כגון קובצי הגדרות, תבניות (templates) ואתרים סטטיים. השתמשו ב-named volume עבור נתונים שהיישום מנהל, כגון קובצי מסד נתונים, אינדקסים של חיפוש ומדיה שהועלתה. ה-bind mount מצביע על נתיב במערכת המארחת (host) שניתן לפתוח בעורך טקסט. ה-named volume הוא אמצעי אחסון ש-Docker יוצר ומנהל עבורכם, והגישה אליו מתבצעת דרך Docker.

שניהם מופיעים תחת אותו מפתח volumes: בתוך הגדרת השירות, וזו הסיבה לבלבול ביניהם. ההבדל טמון בצד השמאלי של הנקודתיים. צד שמאלי שמתחיל ב-. או ב-/ הוא נתיב במערכת המארחת, ולכן מדובר ב-bind mount. כל ערך אחר הוא שם, ולכן מדובר ב-named volume; שם זה חייב להיות מוצהר גם בבלוק ה-volumes: ברמה העליונה של הקובץ.

שתי התחבירים בקובץ compose

services:
  db:
    image: postgres:17
    environment:
      POSTGRES_PASSWORD: changeme
    volumes:
      - pgdata:/var/lib/postgresql/data
  web:
    image: nginx:1.27
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
      - ./site:/usr/share/nginx/html:ro

volumes:
  pgdata:

pgdata:/var/lib/postgresql/data הוא volume בעל שם. ./nginx.conf:/etc/nginx/nginx.conf הוא bind mount, ו־:ro מעגן אותו לקריאה בלבד, שזו ברירת המחדל הנכונה עבור קובץ הגדרות שהמכולה אינה אמורה לשנות. אם תשכחו את הגדרת ה־volumes: ברמה העליונה, Compose ייעצר עם שגיאת service "db" refers to undefined volume pgdata.

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

docker compose up -d
docker volume ls

ה־volume לא נקרא pgdata. הוא נקרא <project>_pgdata, כאשר שם הפרויקט כברירת מחדל הוא שם התיקייה המכילה את קובץ ה־compose. תיקייה בשם myapp תפיק את myapp_pgdata. זה משמעותי מכיוון ששינוי שם התיקייה יצור עבורכם volume חדש וריק, והיישום ייראה כאילו איבד את הנתונים שלו. הוא לא איבד אותם: ה־volume הישן עדיין מופיע ברשימה של docker volume ls. קבעו את השם באמצעות name: בקובץ ה־compose, או הגדירו COMPOSE_PROJECT_NAME, אם ייתכן שהתיקייה תועבר. הגדרות מסוג זה שייכות לשאר ה-קבצי סביבה וסודות של Compose.

מדוע שגיאות הרשאות נפוצות בעיקר ב-bind mounts

זהו ההבדל המעשי המשמעותי ביותר, והוא נובע בעיקרו מכלל אחד: volume בעל שם שנותר ריק בשימוש הראשון מאותחל מתוך ה-image, בעוד ש-bind mount לעולם אינו עובר תהליך כזה.

כאשר Docker מבצע mount ל-volume ריק מעל ספרייה שכבר מכילה תוכן בתוך ה-image, הוא מעתיק את התוכן הזה לתוך ה-volume, תוך שמירה על הבעלות וההרשאות שהוגדרו ב-image. ה-image הרשמי של Postgres מגיע עם /var/lib/postgresql/data בבעלות המשתמש postgres שלו, לכן ה-volume שנוצר יהיה בבעלות אותו מזהה מספרי, ומסד הנתונים יעלה כראוי.

אותו אתחול הוא היתרון היחיד שיש ל-volume בעל שם. Docker מנהל את המיקום הפיזי של ה-volume, אך מכולות קוראות וכותבות אליו באמצעות פעולות מערכת קבצים רגילות, ולכן הבעלות וההרשאות נאכפות בדיוק כפי שהן נאכפות ב-bind mount. volume בעל שם שמוגדר בנתיב שלא קיים ב-image, או כזה שמולא לראשונה על ידי image או משתמש אחר, לא יקבל הגדרות בעלות מה-image, ותהליך שאינו root עלול להיתקל בסירוב גישה גם שם. בדיקת ה-id להלן רלוונטית לשני הסוגים.

ב-bind mount המצב הפוך. המכולה רואה את מה שקיים במערכת המארחת (host), כולל הבעלות, והתוכן המקורי של ה-image באותו נתיב מוסתר. אם ספריית ה-host אינה קיימת, ה-daemon של Docker יוצר אותה, ומכיוון שה-daemon רץ כ-root, הספרייה תהיה בבעלות root:root. תהליך במכולה שרץ כמשתמש שאינו root לא יוכל לכתוב אליה:

PermissionError: [Errno 13] Permission denied: '/data/app.db'

הפתרון הוא התאמת המספרים. הבעלות ב-bind mount נבדקת לפי מזהה משתמש מספרי (uid), ולא לפי שם, כיוון שלכל מכולה יש את ה-/etc/passwd שלה. משתמש בשם app בתוך המכולה אינו בעל משמעות ב-host. מזהה 1000 הוא 1000 בשני הצדדים, אלא אם ה-daemon רץ עם מיפוי מחדש של מרחבי שמות משתמשים (userns-remap, מוגדר ב-/etc/docker/daemon.json או בשורת הפקודה של dockerd). מיפוי מחדש מסיט כל מזהה של מכולה לטווח משני ב-host. בטווח לדוגמה של Docker המתחיל ב-231072, מזהה 0 של המכולה הוא 231072 ב-host, לכן מזהה 1000 של המכולה הוא 232072 ב-host.

id -u
mkdir -p ./data
docker compose exec web id
sudo chown -R 1000:1000 ./data

הפקודה docker compose exec web id מציגה את ה-uid שבו רץ תהליך המכולה בפועל. ללא מיפוי מחדש, התאימו את ספריית ה-host למספר זה, או קבעו את המכולה למספר שלכם באמצעות user: "1000:1000" בהגדרת השירות. עם מיפוי מחדש פעיל, user: לא יכול לקבוע את המכולה למספר ה-host שלכם, כיוון שהמזהה שהגדרתם מוסט גם הוא: user: "1000:1000" עדיין יתורגם ל-232072 ב-host בדוגמה זו. בצעו chown לספריית ה-host למזהה ה-host הממופה (הטווח שלכם נמצא ב-/etc/subuid וב-/etc/subgid). התיעוד של Docker מציין רק את --userns=host ב-docker container create, run או exec כדרך לבטל את המיפוי מחדש עבור מכולה בודדת. קביעת user: היא נקייה יותר עבור יישום שכתבתם בעצמכם. ביצוע chown לספריית ה-host בטוח יותר עבור image שלא כתבתם, כיוון שחלק מה-images מריצים entrypoint כ-root, מוותרים על הרשאות, ומצפים לבעלות ספציפית בנתיב.

כדאי להכיר שני מכשולים נוספים. ב-Fedora, RHEL ומערכות אחרות עם SELinux (אבטחה משופרת ל-Linux) במצב אכיפה, גישה ל-bind mount תיחסם עד לשינוי התווית (relabel), לכן הוסיפו :z עבור נתיב משותף בין מכולות או :Z עבור נתיב המיועד למכולה אחת בלבד, בכתיב - ./data:/data:Z. בנוסף, bind mount של קובץ בודד, במקום ספרייה, עלול להישבר כאשר עורך טקסט מחליף את הקובץ במקום לכתוב בתוכו, כיוון שה-mount עוקב אחרי ה-inode המקורי. המכולה תמשיך לראות את התוכן הישן עד לביצוע restart. בצעו mount לספריית האב אם הקובץ נערך לעיתים קרובות.

ביצועים: היכן קיים הפער בפועל

בשרת Linux, שני הסוגים עוברים דרך אותו נתיב ב־kernel, לכן הבדל התעבורה זניח מכדי להוות שיקול בבחירה. Named volumes המשתמשים בדרייבר ברירת המחדל local נמצאים על אותה מערכת קבצים של שאר Docker, תחת /var/lib/docker/volumes/, בעוד ש־bind mount נמצא בכל מקום אליו הפנית אותו.

הפער מופיע ב־Docker Desktop עבור macOS ו־Windows, שם המכולות רצות בתוך מכונה וירטואלית. שם, bind mount חוצה את מערכת הקבצים של המארח אל תוך המכונה הווירטואלית דרך שכבת שיתוף קבצים, ועומסי עבודה עם פעולות רבות על קבצים קטנים, כמו עץ תלויות של Node.js או מטמון של framework ב־PHP, מואטים באופן מורגש. Named volumes נשארים בתוך המכונה הווירטואלית ואינם סופגים את העלות הזו. זו הסיבה שקובצי compose רבים לפיתוח מבצעים bind-mount לספריית המקור אך מצהירים על named volume עבור node_modules.

ההבדל הממשי הנוסף הוא היכן נשמרים הבתים. bind mount ל־/mnt/backup מציב נתונים על כונן זה. named volume ממוקם על כל מערכת קבצים שמחזיקה את /var/lib/docker, שבדרך כלל ב־VPS היא כונן ה־root. מסד נתונים שגדל בתוך named volume ממלא את אותו כונן שבו נמצאים לוגי המערכת שלכם. בדקו זאת לפני שזה הופך לתקרית:

docker system df -v
df -h /var/lib/docker

docker system df -v מפרט כל volume עם הגודל שלו, ומסמן את אלו שאף מכולה כבר לא מפנה אליהם.

בדיקת נפח (volume) בעל שם

נפח בעל שם אינו קופסה שחורה. ניתן לשאול את Docker היכן הוא נמצא:

docker volume inspect myapp_pgdata

השדה Mountpoint מספק נתיב מארח אמיתי, בדרך כלל /var/lib/docker/volumes/myapp_pgdata/_data. ניתן לקרוא אותו באמצעות sudo ls, וזה שימושי לבדיקה מהירה. אין להתייחס אליו כאל מקום לעריכת קבצים. כתיבה לשם כ-root יוצרת מחדש את בעיית הבעלות שתוארה לעיל, והנתיב הוא פרט מימוש של מנהל ההתקן local, שאינו קיים במנהלי התקן אחרים של נפחים.

הדרך הבטוחה להסתכל פנימה היא באמצעות מכולה זמנית שמעגנת את הנפח:

docker run --rm -v myapp_pgdata:/vol alpine ls -la /vol

שיטה זו עובדת עבור כל מנהל התקן, רואה את אותן הרשאות שהמכולה האמיתית רואה, ולא משאירה דבר מאחור בזכות --rm.

גיבוי של כל סוג

Bind mount הוא ספרייה רגילה, לכן כל כלי לגיבוי ברמת הקבצים מטפל בו ללא הגדרה נוספת. יש להפנות את הגיבוי לנתיב במארח וזה הכל. Named volume דורש שלב נוסף, כיוון שהכלי חייב לגשת לתוכו. יש לעגן (mount) את ה-volume וספרייה במארח לתוך מכולה זמנית, ולאחר מכן ליצור ארכיון:

docker run --rm \
  -v myapp_pgdata:/data:ro \
  -v "$PWD":/backup \
  alpine tar czf /backup/pgdata.tar.gz -C /data .

כדי לשחזר, יש לבצע את הפעולה ההפוכה לתוך volume חדש:

docker volume create myapp_pgdata_restored
docker run --rm \
  -v myapp_pgdata_restored:/data \
  -v "$PWD":/backup \
  alpine tar xzf /backup/pgdata.tar.gz -C /data

tar שומר על הבעלות המספרית (numeric ownership) כאשר הוא רץ כ-root בתוך המכולה, וזה מה שמאפשר ליישום להשתמש ב-volume המשוחזר.

אזהרה אחת תקפה לשני הסוגים. העתקת קבצי מסד נתונים בזמן שהמסד פעיל יוצרת ארכיון של נתונים משתנים, מה שעלול להוביל לשחזור במצב פגום. יש לעצור את השירות תחילה, או לבצע dump באמצעות הכלי הייעודי של מסד הנתונים, כפי שמופיע ב-docker compose exec -T db pg_dump -U postgres appdb > appdb.sql. פעולה זו מייצרת קובץ פשוט שניתן לכלול בתוך שגרת גיבוי מוצפנת של restic לצד קובצי ה-compose שלכם.

העברת bind mount לנפח (volume) בעל שם

התהליך הוא העתקה, לא שינוי שם, והוא אורך כדקה.

docker compose down
docker volume create myapp_pgdata
docker run --rm \
  -v "$PWD/data":/from \
  -v myapp_pgdata:/to \
  alpine sh -c 'cp -a /from/. /to/'

cp -a שומר על הבעלות, ההרשאות וחותמות הזמן, כך שמשתמש המכולה שהיה מסוגל לקרוא את הספרייה הישנה יוכל לקרוא גם את הנפח החדש. לאחר מכן, שנו את השירות כך שישתמש ב-pgdata:/var/lib/postgresql/data, הוסיפו את pgdata לבלוק ה-volumes: ברמה העליונה, הריצו את docker compose up -d, וקראו את הלוגים של היישום לפני מחיקת הספרייה הישנה. ביצוע הפעולה בכיוון ההפוך משתמש באותה פקודה כאשר /from ו-/to מוחלפים ביניהם.

זכרו דבר אחד בזמן הבדיקות. docker compose down מותיר נפחים בעלי שם ללא שינוי, אך docker compose down -v מוחק כל נפח בעל שם שמוגדר בפרויקט, ואין אפשרות לבטל פעולה זו. bind mount שורד את שתי הפקודות, כיוון ש-Docker מעולם לא היה הבעלים של אותה ספרייה. אם פקודות מחזור החיים עדיין חדשות לכם, ה-מדריך יסודות Docker Compose עבור VPS סוקר אותן בפירוט.

בחירה, שירות אחר שירות

שאלו את עצמכם מי כותב את הקובץ. קובץ תצורה שאתם עורכים בעורך טקסט ומבצעים לו commit ל-git שייך ל-bind mount, המותקן ב-:ro, כיוון שאתם רוצים שהוא יהיה גלוי ומנוהל בגרסאות. מצב (state) של יישום שאינכם פותחים ידנית שייך ל-named volume, כיוון ש-Docker מגדיר את ההרשאות מתוך ה-image והנתונים אינם תלויים בנתיב על ה-host.

המקרה המעורב הוא מדיה. ספריית תמונות נכתבת על ידי היישום אך גם מנוהלת על ידכם, ולעיתים קרובות היא גדולה מספיק כדי להצדיק דיסק ייעודי. בצעו לה bind mount לנתיב באותו דיסק, והגדירו את הבעלות עליו באופן מכוון פעם אחת. זהו הדפוס שרוב מערכי ה-self-hosted מאמצים: named volumes עבור מסדי נתונים ו-caches, ו-bind mounts עבור קובצי תצורה ועבור הספריות הגדולות שחשובות לכם. שירות תמיכה כמו Chatwoot הרץ על VPS מגיע בדיוק למבנה הזה, עם Postgres ב-named volume והקבצים המצורפים שהועלו בנתיב שניתן להפנות אליו גיבוי.

FAQ

מה ההבדל בין bind mount לבין named volume?

bind mount ממפה נתיב ב-host לתוך המכולה, כך ששני הצדדים רואים את אותה ספרייה וניתן לערוך אותה בכלים רגילים. named volume הוא שטח אחסון ש-Docker יוצר ומנהל, המזוהה בשם ומוגדר בבלוק ה-volumes: ברמה העליונה. ההבדל המעשי הוא בבעלות: השתמשו ב-bind mount עבור הגדרות שאתם מתחזקים, וב-named volume עבור נתונים שהיישום מתחזק.

מדוע אני מקבל "permission denied" ב-bind mount אך לא ב-named volume?

named volume ריק מאותחל מתוך ה-image, לכן הוא בדרך כלל יורש את הבעלות שהוגדרה ב-image והמשתמש במכולה יכול לכתוב אליו. הוא עדיין אוכף הרשאות, לכן הוא עלול להיכשל באותו אופן אם אין ב-image תוכן בנתיב זה. bind mount מציג את ספריית ה-host כפי שהיא, ואם Docker נדרש ליצור את הספרייה, הוא יצר אותה בבעלות root. הריצו docker compose exec <service> id כדי לראות את המזהה המספרי שבו משתמשת המכולה, לאחר מכן בצעו sudo chown -R <uid>:<gid> לספריית ה-host, או, כאשר ה-daemon אינו מבצע מיפוי מחדש של מרחבי שמות, הגדירו user: "1000:1000" בשירות. אם ה-daemon משתמש במיפוי מחדש, בצעו chown למזהה ה-host הממופה, כיוון שהמזהים של המכולה מוסטים ב-host ו-user: לא משנה זאת.

היכן Docker מאחסן named volumes על הדיסק?

עם הדרייבר המוגדר כברירת מחדל local, הם נמצאים תחת /var/lib/docker/volumes/<volume>/_data, והפקודה docker volume inspect <volume> תציג את ה-Mountpoint המדויק. קראו אותו אם עליכם לבדוק משהו, אך כתבו אליו רק דרך מכולה, כיוון שעריכה כ-root ב-host משנה את הבעלות באופן שהמכולה לא תצפה לו.

כיצד ניתן לגבות named volume?

הריצו מכולה זמנית עם ה-volume וספריית host ששניהם מחוברים (mounted), ולאחר מכן בצעו ארכוב מאחד לשני עם docker run --rm -v myvol:/data:ro -v "$PWD":/backup alpine tar czf /backup/myvol.tar.gz -C /data .. עבור מסד נתונים, בצעו dump באמצעות הכלי הייעודי של מסד הנתונים במקום העתקת קבצים חיים, כיוון שהעתקת קבצים בזמן כתיבה עלולה להוביל לשחזור במצב פגום.

האם הפקודה docker compose down מוחקת את ה-volumes שלי?

docker compose down מסירה מכולות ורשתות ומשאירה את ה-named volumes במקומם. docker compose down -v מוחקת גם כל named volume שהפרויקט מגדיר, לצמיתות. bind mounts לעולם אינם מוסרים על ידי אף אחת מהפקודות הללו, כיוון שהספרייה שייכת ל-host ולא ל-Docker.

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