SSD Nodes Learn 🎉 VPS من $4.99/شهر
الأدلة Matt Connorبقلم Matt Connor

ما هي PUID و PGID في Docker Compose وكيف تضبطها؟

ليست إعدادات في Docker بل اصطلاح في صور linuxserver.io. اكتشف لماذا تظهر ملفاتك بملكية 911 وكيف تضبط PUID و PGID بشكل صحيح لتجنب مشاكل الصلاحيات في مجلدات الربط.

ما هي PUID و PGID فعلياً

تُعد PUID و PGID متغيري بيئة تقرأهما صور حاويات معينة عند بدء التشغيل. لا ينظر Docker نفسه إلى هذه المتغيرات أبداً. إنها مجرد اصطلاح تستخدمه صور linuxserver.io وعدد قليل من الصور الأخرى، لذا فإن أي صورة لم تُبرمج لقراءة هذه المتغيرات ستتجاهلها بصمت.

داخل صورة linuxserver.io، يوجد مستخدم يسمى abc، يتم إنشاؤه وقت بناء الصورة بمعرف مستخدم (UID) 911 ومعرف مجموعة (GID) 911. تبدأ الحاوية بصلاحيات root، ثم تُشغّل نصوص الإعداد الأولية (init scripts)، ويقوم أحد هذه النصوص بإعادة تعيين أرقام هذا المستخدم قبل حدوث أي شيء آخر:

groupmod -o -g "${PGID}" abc
usermod -o -u "${PUID}" abc

يسمح العلم -o باستخدام معرف مستخدم مستخدم بالفعل في مكان آخر. بعد ذلك، تتخلى عملية الإعداد عن صلاحياتها وتُشغّل التطبيق بصلاحيات المستخدم abc. لذا، لا يصل PUID=1000 إلى Docker أبداً. يقوم المتغير بإعادة تعيين رقم المستخدم داخل الحاوية قبل بدء التطبيق، مما يعني أن كل ملف يكتبه التطبيق سيظهر على قرصك بملكية المستخدم 1000. إذا تركت PUID غير مضبوط، فسيحتفظ abc بالقيمة 911، وهذا هو سبب امتلاء مجلدات الربط (bind mounts) غير المهيأة بملفات مملوكة للمستخدم 911:911.

احصل على رقميك باستخدام id

نفّذ هذا الأمر على المضيف، بصفتك المستخدم الذي يملك أدلة البيانات:

id
uid=1000(deploy) gid=1000(deploy) groups=1000(deploy),27(sudo),988(docker)

uid هو PUID الخاص بك و gid هو PGID الخاص بك. بالنسبة للسكربتات، يقوم id -u و id -g بطباعة الأرقام مجردة. في معظم صور VPS الجديدة، يكون حساب المستخدم البشري الأول هو 1000:1000، لكن لا تفترض ذلك. الخادم الذي أُعيد بناؤه، أو الحساب الثاني الذي أُضيف لاحقاً، يعطي 1001 أو أعلى، والرقم الخاطئ هنا هو سبب الخطأ بالكامل. إذا كانت خدماتك تعمل تحت حساب خدمة مخصص بدلاً من مستخدم تسجيل الدخول الخاص بك، نفّذ id thatuser وخذ الأرقام من هناك.

لماذا تظهر ملفاتك بالمعرّف 911:911

يطبع ls -l معرّفاً رقمياً بدلاً من اسم المستخدم عندما لا يطابق هذا المعرّف أي حساب على الخادم. لا يوجد أي شيء على خادمك يحمل UID 911، لذا لا يوجد اسم ليتم عرضه. استخدم ls -ln لرؤية الأرقام في كل مرة وإزالة الغموض:

ls -ln /srv/appdata/sonarr
drwxr-xr-x 2 911 911 4096 Aug  7 09:12 Backups
-rw-r--r-- 1 911 911  512 Aug  7 09:12 config.xml

تفيد هذه المخرجات بأن الحاوية عملت بالإعدادات الافتراضية المدمجة. تأكد من ذلك من داخل الحاوية بدلاً من التخمين:

docker exec sonarr id abc
docker compose logs sonarr | head -n 25

يطبع نظام تهيئة linuxserver نتيجته في سجل بدء التشغيل كسطرين:

User UID:    911
User GID:    911

إذا كانت هذه الأسطر تقرأ 911 بعد أن قمت بضبط PUID=1000 في ملف Compose الخاص بك، فهذا يعني أن المتغير لم يصل إلى الحاوية. السبب المعتاد هو أنك قمت بتعديل docker-compose.yml ثم نفذت docker compose restart، الذي يعيد استخدام الحاوية الموجودة ببيئتها الأصلية. تتطلب تغييرات البيئة تنفيذ docker compose up -d، الذي يعيد إنشاء الحاوية.

لماذا لا يمكنك حذف ملف أنشأته الحاوية

يقارن النواة (kernel) الأرقام، ولا يقارن الأسماء أبداً. يعمل غلافك (shell) بصلاحية UID 1000، بينما يعود الملف إلى UID 911. المجلد الذي يحتوي الملف هو drwxr-xr-x ويعود أيضاً إلى 911، لذا تحصل المجموعات والمستخدمون الآخرون على صلاحية القراءة والتنفيذ فقط دون الكتابة. يتطلب حذف ملف امتلاك صلاحية الكتابة على المجلد الذي يحتويه، وليس على الملف نفسه، لذا تظهر لك هذه المشكلة حتى لو بدا الملف غير ضار:

rm: cannot remove '/srv/appdata/sonarr/config.xml': Permission denied

تصطدم الحاوية التي تحاول الكتابة بنفس العائق من الجهة الأخرى. إذا كان مجلد المضيف يعود لمستخدمك بنمط 755 والتطبيق يعمل بصلاحية 911، فإن أول عملية كتابة ستفشل مع Permission denied وسيعلن التطبيق عن ذلك بأسلوبه الخاص. في تطبيقات .NET مثل Sonarr أو Radarr، يظهر هذا الخطأ كـ UnauthorizedAccessException: Access to the path '/data/downloads' is denied. تخبرك سلسلة الصلاحيات الموجودة أمام الملف بأي من مجموعات الصلاحيات الثلاث يتم تقييمك، وقراءة drwxr-xr-x بشكل صحيح هي ما يحوّل هذا الخطأ من لغز إلى أمر واضح.

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

إصلاح دليل بملكية خاطئة بالفعل

يؤدي تعيين PUID و PGID إلى تغيير سلوك التطبيق من الآن فصاعداً. هذا لا يصحح بأثر رجعي الملفات الموجودة بالفعل على القرص. أوقف الحزمة (stack)، وصحح الملكية بنفسك، ثم ابدأ تشغيلها مجدداً:

docker compose down
sudo chown -R 1000:1000 /srv/appdata/sonarr
docker compose up -d

استخدم sudo chown -R "$(id -u):$(id -g)" /srv/appdata/sonarr إذا كنت تفضل عدم كتابة الأرقام يدوياً. نفذ هذا والإصدار (container) متوقف، لأن التطبيق الذي يعمل أثناء عملية كتابة في منتصف تنفيذ chown متكرر قد ينتهي به الأمر بشجرة أدلة مصححة جزئياً، مما يسبب جولة ثانية مربكة من الأخطاء.

ما لا تعالجه قيم PUID و PGID

هذا هو الجزء الذي يقع فيه المستخدمون الذين نفذوا كل الخطوات بشكل صحيح. يقوم سكربت التهيئة الخاص بـ linuxserver بتغيير ملكية ثلاثة مسارات فقط عند بدء التشغيل وهي: /app و /config و /defaults. مسارات الوسائط (media mounts) الخاصة بك ليست ضمن هذه القائمة. يتم تمرير /data و /downloads و /tv إلى التطبيق دون أي تعديل، لذا إذا كانت ملكية تلك المسارات على المضيف لا تسمح لمستخدم الحاوية بالكتابة، فستبدأ الحاوية بشكل سليم، وتطبع معرف المستخدم (UID) الصحيح في شعارها، ثم تفشل عند أول عملية استيراد.

هذا هو السلوك الصحيح. فإجراء عملية chown متكررة على مكتبة وسائط بحجم اثني عشر تيرابايت عند كل تشغيل للحاوية سيكون كارثياً. هذا يعني أن مسؤولية مجلدات الوسائط تقع على عاتقك، وهي المسارات التي تحدث فيها مشاكل الصلاحيات فعلياً.

ثلاث طرق للتحكم في المستخدم، ومتى تُستخدم كل منها

متغيرات البيئة PUID و PGID

تعمل هذه الطريقة فقط مع الصور التي تقرأ هذه المتغيرات عند نقطة الدخول (entrypoint). تحظى هذه الطريقة بشعبية لأن الحاوية تبدأ بصلاحيات root، وتُجري إعداداتها الخاصة، وتُصلح /config، ثم تتخلى عن الصلاحيات لاحقاً. تستمر Docker Mods ونصوص التهيئة المخصصة في العمل. العيب هو أنك تعتمد على اصطلاح برمجي بدلاً من ميزة في المنصة، كما أن أسماء المتغيرات ليست موحدة عبر المشاريع المختلفة.

المفتاح user: في Compose

هذه ميزة حقيقية في Docker وتعمل مع أي صورة، لأن بيئة تشغيل الحاوية تطبقها قبل تنفيذ كود الصورة نفسه:

services:
  sonarr:
    image: lscr.io/linuxserver/sonarr:latest
    user: "1000:1000"

لا تعمل العملية بصلاحيات root أبداً، ولا حتى للحظة واحدة، مما يمثل مكسباً أمنياً حقيقياً. يؤدي هذا أيضاً إلى تعطيل أي شيء في نقطة الدخول يحتاج إلى صلاحيات root. في صور linuxserver، يدعم المشروع هذه الميزة على أساس "بذل الجهد المعقول" وفقط للصور التي تم اختبارها، وهناك محاذير محددة: تتوقف PUID و PGID عن التأثير، ولن تعمل Docker Mods، ولن تعمل الخدمات المخصصة، وتصبح أنت المسؤول عن الصلاحيات في كل مجلد مثبت (mounted volume). النمط الموثق لديهم يقرن هذا الخيار بـ /run قابل للكتابة:

user: 1000:1000
tmpfs:
  - /run:uid=1000,gid=1000,exec
security_opt:
  - no-new-privileges=true

هناك أثر جانبي شكلي يفاجئ المستخدمين. المعرف الرقمي user: لا يملك إدخالاً مطابقاً في ملف /etc/passwd الخاص بالحاوية، لذا تُبلغ الأدوات داخلها عن whoami: cannot find name for user ID 1000. المعرف صالح والوصول إلى الملفات يعمل بشكل طبيعي، فقط البحث عن الاسم هو الذي يفشل.

وضع Rootless Docker

يقوم Rootless Docker بتشغيل الـ daemon نفسه كمستخدم غير متميز (unprivileged user)، لذا لا يوجد شيء على الخادم يعمل بصلاحيات root الحقيقية. هذا يغير حسابات الملكية بالكامل. يتم تعيين UID 0 في الحاوية إلى UID الخاص بالمستخدم الذي يشغل Rootless Docker على الخادم المضيف، بينما يتم تعيين UID في الحاوية n لأي n يساوي 1 أو أكثر إلى subuid + (n - 1)، حيث subuid هو أساس النطاق المخصص لك في /etc/subuid و /etc/subgid. يتوقع Docker وجود 65,536 معرفاً فرعياً على الأقل هناك.

أعد قراءة هذا التعيين، لأنه يقلب النصيحة المعتادة. في وضع Rootless Docker، الحاوية التي تكتب بصلاحيات root تنتج ملفات مملوكة لك. أما الحاوية التي تكتب بصلاحيات UID 1000 فتنتج ملفات مملوكة لمعرف فرعي حول 100999، وهو ما لا يمكن لـ shell الخاص بك الوصول إليه. لذا، قيمة PUID الصحيحة في الـ daemon العادي هي قيمة خاطئة هنا. الآليتان تحلان نفس المشكلة في طبقات مختلفة، ودمجهما دون تحقق هو السبب في انتهاء الأمر بمجلدات تحتاج إلى sudo لحذفها. إذا انتقلت إلى Rootless، اختبر ملكية ملف واحد مكتوب على خادمك قبل نقل مكتبة كاملة إليه.

بالنسبة لمعظم حزم التطبيقات ذاتية الاستضافة على خادم VPS واحد، فإن استخدام PUID و PGID مع daemon يعمل بصلاحيات root هو الخيار العملي، لأنه ما بُنيت الصور من أجله وما وُثقت لأجله. استخدم user: عندما يذكر ملف README الخاص بالصورة أنها مختبرة لهذا الغرض، أو عندما تشغل صورة رسمية من المصدر لا تدعم PUID إطلاقاً.

حالة حزمة الوسائط: مجموعة واحدة مشتركة عبر الحاويات

تصبح هذه المسألة عملية عند التعامل مع حزمة وسائط arr التي تضم Sonarr وRadarr وعميل تحميل. يكتب عميل التحميل ملفاً مكتملاً في /data/downloads. بعد ذلك، يقوم Sonarr بإنشاء رابط صلب (hardlink) أو نقل ذلك الملف إلى /data/media. لكي يعمل الرابط الصلب، تحتاج الحاويتان إلى صلاحية الكتابة في نفس المسار. إذا كان عميل التحميل يعمل بمعرف المستخدم 1000 بينما يعمل Sonarr بمعرف 1001، فستمتلك إحداهما ملفات لا يمكن للأخرى سوى قراءتها.

الحل هو استخدام مجموعة مشتركة تستخدمها كل حاوية في الحزمة بصفتها PGID الخاص بها:

sudo groupadd -g 13000 media
sudo usermod -aG media deploy
sudo chown -R deploy:media /srv/media
sudo find /srv/media -type d -exec chmod 2775 {} +
sudo find /srv/media -type f -exec chmod 0664 {} +

الرمز 2 في بداية 2775 هو بت setgid. عند تطبيقه على دليل، فهذا يعني أن كل ملف أو دليل فرعي جديد يتم إنشاؤه بداخله يرث المجموعة media بدلاً من المجموعة الأساسية لمنشئ الملف. وبذلك يستمر هذا الإعداد مع التنزيلات الجديدة دون الحاجة لإعادة تشغيل chown. سجّل الخروج ثم عُد للدخول، أو نفّذ newgrp media، قبل التحقق من صلاحياتك؛ فالمجموعة التي تُضاف عبر usermod -aG لا تظهر في جلسة shell مفتوحة مسبقاً.

داخل الحاوية، تقوم groupmod -o -g 13000 abc بإعادة ترقيم المجموعة abc إلى 13000، بحيث يكتب abc الملفات بنفس معرف المجموعة (GID) الخاص بمجموعة media على المضيف. تحتفظ كل حاوية في الحزمة بمعرف المستخدم PUID الخاص بها وتشارك ذلك الـ PGID الموحد.

بعد ذلك، اضبط UMASK=002 في كل حاوية من حاويات linuxserver ضمن الحزمة. هذه هي الخطوة التي يغفل عنها الكثيرون. القيمة الافتراضية في هذه الصور هي UMASK=022، والتي تزيل بت الكتابة الخاص بالمجموعة من كل ملف جديد، فتصبح الملفات بوضع 0644 ولا تؤدي المشاركة التي أعددتها للتو أي غرض. القيمة 002 تنتج ملفات بوضع 0664 وأدلة بوضع 0775، مما يسمح للمجموعة بالكتابة:

services:
  sonarr:
    image: lscr.io/linuxserver/sonarr:latest
    container_name: sonarr
    environment:
      - PUID=${PUID}
      - PGID=${PGID}
      - UMASK=002
      - TZ=Etc/UTC
    volumes:
      - /srv/appdata/sonarr:/config
      - /srv/media:/data
    restart: unless-stopped

يجب وضع هاتين القيمتين في ملف .env بجوار ملف Compose، بحيث تقرأ الحزمة بأكملها تعريفاً واحداً:

PUID=1000
PGID=13000

يقرأ Compose هذا الملف تلقائياً لاستبدال المتغيرات بنمط ${PUID}، وهو نفس الآلية التي تستخدمها لبيانات الاعتماد. العادات المتعلقة بـ إبقاء القيم خارج ملف docker-compose.yml ووضعها في ملف .env تنطبق هنا أيضاً، مع اختلاف أن هذين الرقمين ليسا سريين.

تحقق من الإعداد من البداية إلى النهاية بدلاً من الوثوق في الإعدادات فقط. اكتب ملفاً من داخل إحدى الحاويات واقرأه من المضيف:

docker exec sonarr touch /data/downloads/permtest
ls -ln /srv/media/downloads/permtest

تظهر النتيجة السليمة معرف المستخدم PUID الخاص بك كمالك، و13000 كمجموعة، و-rw-rw-r-- كنمط للملف. إذا كان وضع المجموعة يظهر 1000، فهذا يعني أن بت setgid مفقود من ذلك الدليل. إذا كان النمط يظهر -rw-r--r--، فهذا يعني أن متغير UMASK لم يدخل حيز التنفيذ، لذا تأكد من أنك قمت بإعادة إنشاء الحاوية بدلاً من مجرد إعادة تشغيلها. احذف ملف الاختبار باستخدام rm /srv/media/downloads/permtest عند الانتهاء.

أي الصور تستخدم أي متغير

تستخدم صور linuxserver.io المتغيرات PUID وPGID وUMASK. بينما يستخدم Paperless-ngx أسماءً مختلفة لنفس المفهوم وهي USERMAP_UID وUSERMAP_GID، وكلاهما يضبط القيمة الافتراضية على 1000، وتوجهك وثائقه لقراءتها من id -u وid -g. العديد من الصور الرسمية، بما في ذلك صور قواعد البيانات وخوادم الويب الشائعة، تأتي بمستخدم مدمج ثابت وتتوقع منك استخدام user: أو تركه كما هو.

لذا، تحقق من ملف README الخاص بكل صورة قبل نسخ كتلة متغيرات البيئة بين المشاريع. يمرر Docker أي متغير بيئة تضبطه إلى أي حاوية، سواء كان ما بداخلها يقرأه أم لا، والمتغير PUID الذي لا يستهلكه أي شيء لا ينتج عنه خطأ أو تحذير أو تأثير. تعمل الحاوية بالمستخدم الذي انتهى إليه ملف Dockerfile الخاص بها، وتكتشف ذلك من خلال ملكية الملفات التي تكتبها.

FAQ

لماذا تظهر ملفات Docker الخاصة بي بملكية 911:911؟

الرقم 911 هو معرف المستخدم (UID) ومعرف المجموعة (GID) للمستخدم abc المدمج في صور linuxserver.io. ظهور هذا الرقم يعني أن الحاوية بدأت دون ضبط PUID و PGID، لذا أبقى نص الإقلاع البرمجي (init script) على القيم الافتراضية المدمجة. يعرض ls -l الأرقام الخام لأنّه لا يوجد حساب على مضيفك يحمل المعرف 911، وبالتالي لا يوجد اسم لعرضه. اضبط PUID و PGID على مخرجات أمر id، ثم أعد إنشاء الحاوية باستخدام docker compose up -d، وبعد ذلك صحّح ملكية الملفات الموجودة باستخدام sudo chown -R 1000:1000 على المجلد المتأثر.

هل تعمل PUID و PGID مع كل صور Docker؟

لا. هذه ليست ميزة في Docker ولا يقرؤها Docker أبداً. هي تعمل فقط مع الصور التي يقرأ نص نقطة الدخول (entrypoint) الخاص بها هذه المتغيرات ويستدعي usermod و groupmod قبل بدء التطبيق، وهذا ينطبق على عائلة صور linuxserver.io وبعض المشاريع التي نسخت هذا النمط. تستخدم مشاريع أخرى أسماء مختلفة، مثل USERMAP_UID و USERMAP_GID في مشروع paperless-ngx. في الصور التي لا تقرأ أياً منهما، يتم قبول المتغيرات وتجاهلها دون أي تحذير.

هل يجب عليّ استخدام PUID و PGID أم مفتاح user: في Docker Compose؟

استخدم PUID و PGID عندما تدعم الصورة ذلك، لأن نقطة الدخول تظل تعمل بصلاحيات root لفترة كافية لإصلاح /config وبدء خدماتها الخاصة بشكل صحيح. استخدم user: عندما لا تدعم الصورة PUID، أو عندما ينص ملف README الخاص بالصورة على أنها مختبرة للعمل بدون صلاحيات root. في صور linuxserver، يؤدي ضبط user: إلى جعل PUID و PGID غير فعّالين، ويوقف عمل Docker Mods والخدمات المخصصة، ويجعل مسؤولية صلاحيات كل المجلدات المربوطة (mounted volumes) تقع على عاتقك بالكامل.

يمتلك Sonarr معرف PUID الصحيح لكنه لا يزال غير قادر على نقل الملفات. ما المشكلة؟

تحقق من ثلاثة أمور بالترتيب. أولاً، نقطة ربط الوسائط (media mount) نفسها: نص الإقلاع يغير ملكية /app و /config و /defaults فقط، لذا يحتفظ /data أو /downloads بملكيته الأصلية على المضيف. ثانياً، المجموعة المشتركة: إذا كان عميل التحميل و Sonarr يعملان بمعرفات GID مختلفة، فلن يتمكن أي منهما من تعديل ملفات الآخر، لذا امنح كل حاوية في حزمة العمل نفس PGID. ثالثاً، قناع الصلاحيات (umask): القيمة الافتراضية في الصورة UMASK=022 تكتب الملفات بصلاحيات 0644 دون بت الكتابة للمجموعة، مما يلغي فائدة المجموعة المشتركة تماماً. اضبط UMASK=002 وفعّل بت setgid على المجلدات باستخدام chmod 2775 لكي ترث الملفات الجديدة المجموعة تلقائياً.