SSD Nodes Learn 8GB RAM — $66/سنة
الأدلة Matt Connorبقلم Matt Connor · آخر تحديث في 2026-08-01

الفرق بين Bind Mount و Named Volume في Docker Compose

تعرف على الفرق الجوهري بين Bind Mount و Named Volume في Docker Compose. اكتشف متى تستخدم كل نوع لملفات الإعدادات وقواعد البيانات، وكيفية تجنب أخطاء الصلاحيات الشائعة.

Bind mount أو named volume: الإجابة المختصرة

تأتي مجلدات Docker Compose في نوعين، ويعتمد الاختيار على الجهة التي تمتلك الملفات. استخدم bind mount للملفات التي تقرؤها وتعدلها بنفسك، مثل ملفات الإعدادات، والقوالب، والمواقع الساكنة. استخدم named volume للبيانات التي يمتلكها التطبيق، مثل ملفات قواعد البيانات، وفهارس البحث، والوسائط المرفوعة. يشير bind mount إلى مسار على المضيف يمكنك فتحه في محرر نصوص. أما 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 هو مجلد مسمى (named 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

المجلد لا يسمى pgdata. بل يسمى <project>_pgdata، حيث يتم تعيين اسم المشروع افتراضياً ليكون اسم المجلد الذي يحتوي على ملف compose. المجلد المسمى myapp ينتج عنه myapp_pgdata. هذا الأمر مهم لأن إعادة تسمية المجلد ستمنحك مجلداً جديداً فارغاً، وسيبدو التطبيق وكأنه فقد بياناته. هذا غير صحيح: المجلد القديم لا يزال مدرجاً بواسطة docker volume ls. قم بتثبيت الاسم باستخدام name: في ملف compose، أو اضبط COMPOSE_PROJECT_NAME إذا كان من المحتمل نقل المجلد. تنتمي إعدادات كهذه مع بقية ملفات بيئة وأسرار Compose.

لماذا تظهر أخطاء الصلاحيات فقط مع الربط المباشر (bind mounts)

هذا هو الفرق العملي الأكبر، وينتج عن قاعدة واحدة: المجلد المسمى (named volume) الذي يكون فارغاً عند الاستخدام الأول يتم ملؤه تلقائياً بمحتويات الصورة، بينما لا يحدث ذلك أبداً مع الربط المباشر.

عندما يقوم Docker بربط مجلد مسمى فارغ فوق دليل يحتوي بالفعل على ملفات داخل الصورة، فإنه ينسخ تلك المحتويات إلى المجلد، مع الاحتفاظ بملكية وصلاحيات الملفات كما حددتها الصورة. صورة Postgres الرسمية تشحن /var/lib/postgresql/data بملكية المستخدم postgres الخاص بها، لذا يظهر المجلد مملوكاً لنفس المعرف الرقمي، وتبدأ قاعدة البيانات بالعمل.

الربط المباشر يفعل العكس تماماً. يرى الحاوية كل ما هو موجود على المضيف، بما في ذلك الملكية، وتصبح محتويات الصورة في ذلك المسار مخفية. إذا لم يكن دليل المضيف موجوداً، يقوم Docker daemon بإنشائه، وبما أن الـ daemon يعمل بصلاحيات root، فستحصل على دليل مملوك لـ root:root. عندئذ، لا تستطيع عملية الحاوية التي تعمل بمستخدم غير root الكتابة في هذا الدليل:

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

الحل هو مطابقة الأرقام. تتم مقارنة الملكية عبر الربط المباشر بواسطة معرف المستخدم الرقمي (uid)، وليس بالاسم، لأن الحاوية لديها ملف /etc/passwd الخاص بها. المستخدم المسمى app داخل الحاوية لا يعني شيئاً على المضيف. معرف المستخدم 1000 يعني 1000 على كلا الجانبين.

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

يطبع docker compose exec web id معرف المستخدم الذي تعمل به عملية الحاوية فعلياً. طابق دليل المضيف مع هذا الرقم، أو ثبت الحاوية على رقمك باستخدام user: "1000:1000" في الخدمة. تثبيت user: هو الأفضل للتطبيقات التي كتبتها بنفسك. تغيير ملكية دليل المضيف (chowning) أكثر أماناً للصور التي لم تكتبها، لأن بعض الصور تبدأ نقطة الدخول (entrypoint) بصلاحيات root، ثم تتنازل عن الصلاحيات، وتتوقع ملكية محددة للملفات في الداخل.

هناك فخّان إضافيان يجب معرفتهما. على أنظمة Fedora و RHEL والأنظمة الأخرى التي تستخدم SELinux (نظام Linux المعزز أمنياً)، يتم رفض الربط المباشر حتى يتم إعادة تسمية الوسم (relabel)، لذا أضف :z للمسار المشترك بين الحاويات أو :Z للمسار الذي تستخدمه حاوية واحدة فقط، ويُكتب كـ - ./data:/data:Z. كما أن الربط المباشر لملف واحد، بدلاً من دليل، يتعطل عندما يقوم محرر نصوص باستبدال الملف بدلاً من الكتابة فوقه مباشرة، لأن الربط يتبع الـ inode الأصلي. تستمر الحاوية في رؤية المحتوى القديم حتى تقوم بإعادة تشغيلها. قم بربط الدليل الأب إذا كان الملف يُعدل بشكل متكرر.

الأداء: حيث تظهر الفجوة الحقيقية

على خادم Linux، يمر كلا النوعين عبر نفس مسار النواة، لذا فإن فرق الإنتاجية صغير بما يكفي لكي لا تبني اختيارك على هذا الأساس. المجلدات المسماة التي تستخدم برنامج التشغيل الافتراضي local تعيش على نفس نظام الملفات الخاص ببقية Docker، تحت المسار /var/lib/docker/volumes/، بينما يعيش الربط المباشر (bind mount) حيثما وجهته.

تظهر الفجوة في Docker Desktop لنظامي macOS وWindows، حيث تعمل الحاويات داخل جهاز افتراضي. هناك، يعبر الربط المباشر من نظام ملفات المضيف إلى ذلك الجهاز الافتراضي عبر طبقة مشاركة ملفات، وتتباطأ أحمال العمل التي تحتوي على العديد من عمليات الملفات الصغيرة، مثل شجرة تبعيات Node.js أو ذاكرة التخزين المؤقت لإطار عمل PHP، بشكل ملحوظ. تبقى المجلدات المسماة داخل الجهاز الافتراضي ولا تتحمل تلك التكلفة. لهذا السبب تقوم العديد من ملفات compose الخاصة بالتطوير بربط دليل المصدر مباشرة ولكنها تعلن عن مجلد مسمى فوق node_modules.

الفرق الحقيقي الآخر هو مكان تخزين البيانات. الربط المباشر بمسار /mnt/backup يضع البيانات على ذلك القرص. بينما يستقر المجلد المسمى على أي نظام ملفات يحتوي على /var/lib/docker، والذي يكون عادةً قرص الجذر (root disk) في خوادم VPS. قاعدة بيانات تنمو داخل مجلد مسمى ستملأ نفس القرص الذي توجد عليه سجلات نظامك. تحقق من ذلك قبل أن يتحول الأمر إلى حادثة:

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

يسرد الأمر docker system df -v كل مجلد مع حجمه، ويحدد المجلدات التي لم تعد أي حاوية تشير إليها.

فحص وحدة تخزين مسماة

وحدة التخزين المسماة ليست صندوقاً أسود. اسأل 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 إلى خطوة إضافية، لأن الأداة يجب أن تصل إلى محتوياته. قم بربط المجلد ودليل المضيف في حاوية مؤقتة، ثم أنشئ أرشيفًا:

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

قم بالاستعادة عن طريق عكس العملية في مجلد جديد:

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 داخل الحاوية، وهو ما يضمن بقاء المجلد المستعاد قابلاً للاستخدام من قبل التطبيق.

ينطبق تحذير واحد على كلا النوعين. نسخ ملفات قاعدة البيانات أثناء تشغيلها يمنحك أرشيفًا لبيانات متغيرة، وقد يؤدي ذلك إلى استعادة حالة تالفة. أوقف الخدمة أولاً، أو قم بتصدير البيانات عبر أداة قاعدة البيانات الخاصة، كما في docker compose exec -T db pg_dump -U postgres appdb > appdb.sql. ينتج عن ذلك ملف عادي يمكنك بعد ذلك تضمينه في إجراء نسخ احتياطي مشفر باستخدام restic بجانب ملفات compose الخاصة بك.

ترحيل ربط المجلد (bind mount) إلى وحدة تخزين مسماة (named 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 يشرحها بالتفصيل.

الاختيار، خدمة تلو الأخرى

اسأل نفسك من الذي يكتب الملف. الإعدادات التي تعدلها في محرر نصوص وتلتزم بها في git تنتمي إلى bind mount، يتم تثبيتها في :ro، لأنك تريدها مرئية ومؤرشفة في نظام إصدارات. حالة التطبيق التي لا تفتحها يدوياً أبداً تنتمي إلى named volume، لأن Docker يضبط الأذونات بشكل صحيح ولا تعتمد البيانات على مسار المضيف.

الحالة المختلطة هي الوسائط. مكتبة الصور يكتبها التطبيق ولكنك تديرها أيضاً، وغالباً ما تكون كبيرة بما يكفي لتتطلب قرصاً محدداً. قم بعمل bind mount لها على مسار في ذلك القرص، واضبط الملكية بشكل متعمد لمرة واحدة. هذا هو النمط الذي تستقر عليه معظم حزم الاستضافة الذاتية: named volumes لقواعد البيانات وذاكرة التخزين المؤقت، و bind mounts للإعدادات وللمجلد الكبير الذي تهتم به.

FAQ

ما الفرق بين الربط المباشر (bind mount) والمجلد المسمى (named volume)؟

يقوم الربط المباشر بتعيين مسار على المضيف داخل الحاوية، بحيث يرى الطرفان نفس الدليل ويمكنك تعديله باستخدام الأدوات العادية. المجلد المسمى هو وحدة تخزين ينشئها Docker ويديرها، ويشار إليها بالاسم ويتم الإعلان عنها في كتلة volumes: ذات المستوى الأعلى. الفرق العملي يكمن في الملكية: استخدم الربط المباشر للإعدادات التي تحتفظ بها، والمجلدات المسماة للبيانات التي يديرها التطبيق.

لماذا تظهر لي رسالة "permission denied" مع الربط المباشر وليس مع المجلد المسمى؟

يتم ملء المجلد المسمى الفارغ من الصورة، لذا فهو يرث الملكية التي حددتها الصورة ويمكن لمستخدم الحاوية الكتابة فيه. يعرض الربط المباشر دليل المضيف كما هو تماماً، وإذا اضطر Docker لإنشاء ذلك الدليل فإنه يجعله مملوكاً لـ root. نفذ docker compose exec <service> id لرؤية المعرف الرقمي الذي تستخدمه الحاوية، ثم استخدم sudo chown -R <uid>:<gid> على دليل المضيف، أو اضبط user: "1000:1000" في الخدمة.

أين يخزن Docker المجلدات المسماة على القرص؟

مع برنامج التشغيل الافتراضي local، توجد هذه المجلدات تحت /var/lib/docker/volumes/<volume>/_data، ويقوم docker volume inspect <volume> بطباعة Mountpoint الدقيق. اقرأه إذا كنت بحاجة للتحقق من شيء ما، ولكن لا تكتب فيه إلا من خلال حاوية، لأن التعديل بصلاحيات root على المضيف يغير الملكية بطرق قد لا تتوقعها الحاوية.

كيف يمكنني نسخ المجلد المسمى احتياطياً؟

قم بتشغيل حاوية قصيرة العمر مع ربط المجلد ودليل المضيف، ثم قم بالأرشفة من أحدهما إلى الآخر باستخدام docker run --rm -v myvol:/data:ro -v "$PWD":/backup alpine tar czf /backup/myvol.tar.gz -C /data .. بالنسبة لقاعدة البيانات، استخدم أداة قاعدة البيانات الخاصة بها بدلاً من نسخ الملفات الحية، لأن نسخ الملفات أثناء عمليات الكتابة قد يؤدي إلى استعادة حالة تالفة.

هل يقوم الأمر docker compose down بحذف المجلدات الخاصة بي؟

يقوم docker compose down بإزالة الحاويات والشبكات ويترك المجلدات المسماة في مكانها. بينما يقوم docker compose down -v أيضاً بحذف كل مجلد مسمى تم الإعلان عنه في المشروع بشكل دائم. لا يتم حذف الربط المباشر بواسطة أي من الأمرين، لأن ذلك الدليل يخص المضيف وليس Docker.