ملف Docker Compose واحد لتشغيل حزمة arr على VPS
شغّل Prowlarr وSonarr وRadarr وqBittorrent بملف Docker Compose واحد، مع PUID وPGID وتخطيط مجلدات مشترك يحافظ على عمل الروابط الصلبة.
ما الذي ستبنيه
تتكون حزمة arr المعتمدة على Docker Compose من أربع حاويات تدير مكتبة وسائط: يتولى Prowlarr إعدادات الفهارس، ويتولى Sonarr المسلسلات، ويتولى Radarr الأفلام، ويعمل qBittorrent كعميل تنزيل. تتواصل هذه الحاويات عبر شبكة Compose باستخدام أسماء الخدمات، وتشترك في شجرة مجلدات واحدة على الخادم المضيف. عملية التثبيت قصيرة. لكن تخطيط وحدات التخزين هو العامل الذي يحدد ما إذا كانت الحزمة ستعمل لسنوات أو ستسبب لك مشكلات كل أسبوع، لذلك يركّز معظم هذا الدليل عليه.
لا تبحث الحزمة عن المحتوى نيابةً عنك. يحتفظ Prowlarr بأي فهارس تضيفها إليه، واختيار الفهارس التي تستخدمها مسؤوليتك وقرارك. يشرح هذا الدليل المكونات التشغيلية: المستخدمين، والمسارات، والصلاحيات، وشبكات الحاويات، والفحوصات التي تثبت أن كل شيء يعمل.
إذا لم تكتب ملف Compose من قبل، فاقرأ أساسيات Docker Compose لخادم VPS أولاً. يفترض هذا المنشور أن docker compose version يعرض مخرجات بالفعل على خادمك.
لماذا تفشل الروابط الصلبة، ولماذا تكمن المشكلة كلها هنا
عندما ينتهي Sonarr من تنزيل ملف، يستورده إلى مكتبة الوسائط. إذا كان مجلد التنزيل ومجلد المكتبة موجودين على نظام الملفات نفسه، يكون الاستيراد رابطاً صلباً: اسم ثانٍ يشير إلى البيانات نفسها على القرص. لا يستهلك ذلك مساحة إضافية ولا يستغرق وقتاً إضافياً. يواصل torrent المشاركة من الاسم القديم، بينما يقرأ خادم الوسائط الاسم الجديد.
إذا كان المجلدان موجودين على نظامي ملفات مختلفين، فلا يستطيع kernel إنشاء هذا الرابط. عندها يعود Sonarr إلى نسخ الملف. يستغرق موسم حجمه 40 GB الآن 80 GB من مساحة القرص، إضافة إلى عدة دقائق من عمليات الإدخال والإخراج، ويسجل سجل الاستيراد أن إنشاء الرابط الصلب فشل وأن الملف نُسخ بدلاً من ذلك. على VPS ذي مساحة قرص محددة، هذه هي الطريقة التي تنفد بها المساحة خلال أسبوع.
المشكلة الخفية هي التالية. داخل container، يكون bind mount حدّاً فاصلاً بين نظامي ملفات. إذا ركّبت /mnt/data/torrents في /downloads و/mnt/data/media في /tv، فحتى مع وجود المجلدين على قرص واحد في المضيف، يرى Sonarr نقطتي mount منفصلتين ويرفض إنشاء رابط بينهما. توضح وثائق صورة LinuxServer.io الرسمية ذلك مباشرة: استخدام المسارين المنفصلين /downloads و/tv يلغي إمكانية إنشاء روابط صلبة.
الحل هو استخدام mount واحد. يحصل كل container يتعامل مع الوسائط على volume واحد مشترك، /mnt/data:/data، وتكون كل المسارات التي يستخدمها مجلدات داخله. نقطة mount واحدة، ونظام ملفات واحد، وروابط صلبة تعمل.
إنشاء المستخدم والمجموعة والمجلدات
تكتب الحاويات الملفات باستخدام معرّف مستخدم رقمي تحدده PUID وPGID. استخدم حسابك الخاص حتى تتمكن من قراءة هذه الملفات وتحريرها عبر SSH من دون sudo.
id -u
id -gيعرض كلا الأمرين عادةً 1000 على Ubuntu VPS جديد. أنشئ الآن بنية المجلدات. ضعها على القرص الذي يخزّن الوسائط، وحافظ على وجود البنية كاملة على هذا القرص الواحد.
sudo mkdir -p /mnt/data/torrents/movies /mnt/data/torrents/tv
sudo mkdir -p /mnt/data/media/Movies /mnt/data/media/Shows
sudo chown -R 1000:1000 /mnt/data
sudo chmod -R 775 /mnt/dataتحقق من أن البنية موجودة فعلاً على نظام ملفات واحد قبل المتابعة:
df --output=source,target /mnt/data/torrents /mnt/data/mediaيجب أن يعرض السطران جهاز المصدر نفسه. يعني اختلاف الجهازين أن hardlinks لن تعمل أبداً، مهما كانت القيم التي تضبطها في إعدادات الحاوية.
سُمّيت مجلدا المكتبة Movies وShows عن قصد. إذا كنت تشغّل Jellyfin كخادم الوسائط لديك بالفعل، فقم بتركيب /mnt/data/media في Jellyfin على أنه /media، وستوجد مكتبتاه في /media/Movies و/media/Shows، تماماً كما يحددهما ذلك الدليل.
ملف البيئة
احتفظ بالقيم التي تختلف من خادم إلى آخر في .env، بجوار ملف Compose.
mkdir -p ~/arr && cd ~/arrاكتب ~/arr/.env:
PUID=1000
PGID=1000
TZ=Etc/UTC
DATA_ROOT=/mnt/dataاضبط TZ على منطقتك الزمنية، مثل Europe/Berlin. تُجدول تطبيقات arr المهام وتضع المنطقة الزمنية في أسطر السجل، ولذلك تجعل القيمة الخاطئة كل سجل مربكاً لاحقاً.
ملف Compose
اكتب ~/arr/docker-compose.yml:
services:
prowlarr:
image: lscr.io/linuxserver/prowlarr:latest
container_name: prowlarr
environment:
- PUID=${PUID}
- PGID=${PGID}
- TZ=${TZ}
volumes:
- ./config/prowlarr:/config
ports:
- 127.0.0.1:9696:9696
restart: unless-stopped
sonarr:
image: lscr.io/linuxserver/sonarr:latest
container_name: sonarr
environment:
- PUID=${PUID}
- PGID=${PGID}
- TZ=${TZ}
volumes:
- ./config/sonarr:/config
- ${DATA_ROOT}:/data
ports:
- 127.0.0.1:8989:8989
restart: unless-stopped
radarr:
image: lscr.io/linuxserver/radarr:latest
container_name: radarr
environment:
- PUID=${PUID}
- PGID=${PGID}
- TZ=${TZ}
volumes:
- ./config/radarr:/config
- ${DATA_ROOT}:/data
ports:
- 127.0.0.1:7878:7878
restart: unless-stopped
qbittorrent:
image: lscr.io/linuxserver/qbittorrent:latest
container_name: qbittorrent
environment:
- PUID=${PUID}
- PGID=${PGID}
- TZ=${TZ}
- WEBUI_PORT=8080
- TORRENTING_PORT=6881
volumes:
- ./config/qbittorrent:/config
- ${DATA_ROOT}:/data
ports:
- 127.0.0.1:8080:8080
- 6881:6881
- 6881:6881/udp
stop_grace_period: "10s"
restart: unless-stoppedهناك أربعة أمور في هذا الملف تنفّذ العمل الفعلي.
القيمة ${DATA_ROOT}:/data متطابقة في الحاويات الثلاث التي تتعامل مع الوسائط. ولا يحصل Prowlarr عليها، لأن Prowlarr لا يفتح ملف وسائط مطلقاً.
يُربط كل منفذ ويب بالعنوان 127.0.0.1، لذلك ينشره Docker على عنوان loopback فقط. أما استخدام 8989:8989 عادي، فسوف ينشر المنفذ على كل الواجهات، وستمرر قواعد الجدار الناري الخاصة بـDocker تلك الحركة مباشرة متجاوزةً قاعدة deny في ufw. يفاجئ هذا السلوك المستخدمين باستمرار، وهو موضح في سبب نشر Docker للمنافذ مباشرة عبر ufw.
يُنشر المنفذ 6881 على جميع الواجهات عن قصد. هذا هو منفذ الاستماع الخاص بـtorrent، ويجب أن يكون قابلاً للوصول لاتصالات الأقران الواردة. ينطبق سلوك Docker نفسه هنا، لذلك لا تحتاج إلى قاعدة في ufw له، وإضافة قاعدة لن تغيّر شيئاً. توضح وثائق Docker، التي جرى التحقق منها في أكتوبر 2026، أن الحركة إلى منفذ منشور تُوجَّه قبل وصولها إلى السلاسل التي يستخدمها ufw، ولذلك لا تأثير لـsudo ufw allow 6881 في هذا المنفذ. يصبح المنفذ قابلاً للوصول من الإنترنت بمجرد بدء الحاوية، كما أن استخدام deny في ufw لن يغلقه. إذا احتجت في أي وقت إلى تقييد الجهات التي يمكنها الاتصال به، فتشير وثائق Docker إلى استخدام قواعد في سلسلة DOCKER-USER بدلاً من ufw. أبقِ ufw للخدمات التي لا تعمل داخل Docker، مثل SSH، واقرأ أساسيات جدار ufw الناري لخادم VPS إذا كانت هذه الأداة جديدة عليك.
تكون أدلة الإعداد منفصلة لكل تطبيق، ولا تتم مشاركة سوى وحدة تخزين الوسائط. أنشئها قبل التشغيل الأول حتى تكون مملوكةً للمستخدم الخاص بك بدلاً من root:
mkdir -p ~/arr/config/prowlarr ~/arr/config/sonarr ~/arr/config/radarr ~/arr/config/qbittorrent
docker compose up -d
docker compose psيجب أن تقرأ الخدمات الأربع كلها running. اعتباراً من أكتوبر 2026، تُنشر هذه الصور على lscr.io، وتتبع العلامة latest الإصدار المستقر الحالي. لذلك ثبّت علامة إصدار محددة بدلاً منها إذا أردت أن تكون الترقيات قراراً تتخذه، لا مفاجأة.
الوصول إلى واجهات الويب بأمان
بما أن المنافذ مرتبطة بواجهة loopback، فلا شيء مكشوف بعد. مرّرها عبر SSH من جهازك:
ssh -L 9696:127.0.0.1:9696 -L 8989:127.0.0.1:8989 \
-L 7878:127.0.0.1:7878 -L 8080:127.0.0.1:8080 you@your-serverالآن يفتح http://127.0.0.1:8989 في متصفحك واجهة Sonarr على الخادم. للوصول الدائم، ضع المكدس خلف Traefik مع شهادات TLS لعدة تطبيقات، أو صِل إلى الخادم عبر شبكة WireGuard VPN تستضيفها بنفسك. لكن VPN لا تصل إلى هذه المنافذ تلقائياً. توضح وثائق Docker، التي جرى التحقق منها في October 2026، أنه عند نشر منفذ على عنوان localhost، لا يستطيع الوصول إليه إلا مضيف Docker. لاستخدام VPN، واصل تشغيل نفق SSH عبرها، أو غيّر الارتباطات في ملف Compose من 127.0.0.1 إلى عنوان الخادم نفسه على واجهة VPN. لا ينبغي أن يكون أي من هذه التطبيقات على الإنترنت العام مع صفحة تسجيل الدخول الخاصة به فقط كوسيلة حماية. إذا اخترت مسار reverse proxy وفضّلت استخدام حساب واحد لجميع الواجهات الأربع بدلاً من متابعة حسابات تسجيل دخول منفصلة لكل تطبيق، فإن Authentik يوفّر تسجيل دخول موحّداً مستضافاً ذاتياً ويمكن لـTraefik فرضه على كل طلب باستخدام forward auth.
ينشئ qBittorrent كلمة مرور عشوائية للمسؤول عند التشغيل الأول، ويطبعها في سجل الحاوية. اقرأها، ثم غيّرها في واجهة الويب:
docker compose logs qbittorrent | grep -i passwordإذا تخطيت تغييرها، فسيُنشئ كلمة مرور عشوائية جديدة عند كل إعادة تشغيل، وستعود إلى السجلات في كل مرة.
عيّن المسارات داخل كل تطبيق
في qBittorrent، افتح Options، ثم Downloads، وعيّن مسار الحفظ الافتراضي إلى /data/torrents. أبقِ مجلد التنزيلات غير المكتملة داخل الشجرة نفسها، مثل /data/torrents/incomplete. لا يمكن إنشاء hard link لتنزيل يكتمل في أي مكان خارج /data داخل المكتبة.
في Sonarr، افتح Settings، ثم Media Management، وأضف مجلد الجذر /data/media/Shows. يكون مجلد الجذر في Radarr هو /data/media/Movies. هذه مسارات داخل الحاوية. يُرفض مسار المضيف /mnt/data/media/Shows لأن هذا المجلد غير موجود من منظور الحاوية.
في كل من Sonarr وRadarr، افتح Settings، ثم Download Clients، وأضف qBittorrent. المضيف هو qbittorrent والمنفذ هو 8080. يعمل اسم الخدمة كاسم مضيف لأن Compose يضع الحاويات الأربع على شبكة واحدة مع خدمة DNS داخلية (نظام أسماء النطاقات). لا تستخدم localhost هنا: داخل حاوية Sonarr، يكون localhost هو Sonarr.
اترك Remote Path Mappings فارغاً. تُستخدم هذه الميزة لترجمة مسار يبلّغ عنه عميل التنزيل إلى مسار يمكن لتطبيقات arr الوصول إليه. مع وجود mount مشترك واحد هو /data، يتفق كلا التطبيقين بالفعل على كل مسار. وهذا هو السبب الثاني الذي يجعل هذا التخطيط جديراً بالجهد.
ربط Prowlarr بـ Sonarr وRadarr
يدفع Prowlarr تعريفات أدوات الفهرسة إلى التطبيقات الأخرى، لذلك تضبط أداة الفهرسة مرة واحدة بدلاً من ضبطها مرتين. يحتاج Prowlarr إلى مفتاح API (واجهة برمجة التطبيقات) من كل تطبيق.
في Sonarr، افتح Settings، ثم General، وانسخ مفتاح API. في Prowlarr، افتح Settings، ثم Apps، وأضف تطبيق Sonarr، واملأ 3 حقول. قيمة Prowlarr Server هي http://prowlarr:9696. وقيمة Sonarr Server هي http://sonarr:8989. أما API Key فهي القيمة التي نسختها. اضغط Test. تعني النتيجة الخضراء أن Prowlarr وصل إلى Sonarr عبر شبكة Compose. كرر الخطوات مع Radarr باستخدام http://radarr:7878.
تعني النتيجة الحمراء التي تفيد برفض الاتصال في معظم الحالات أن اسم الخدمة غير صحيح أو أن البادئة http:// مفقودة. تأكد من إمكانية حل الاسم من داخل الحاوية:
docker compose exec prowlarr curl -sS -o /dev/null -w '%{http_code}\n' http://sonarr:8989يثبت رمز حالة HTTP أن مسار الشبكة يعمل. ويثبت خطأ حل الاسم أن اسم الخدمة غير صحيح.
تحقّق من إنشاء الروابط الصلبة فعلياً
لا تثق في الإعداد قبل رؤية عدد الروابط. بعد استيراد عنصر واحد، قارن الملف الذي تم تنزيله بملف المكتبة:
stat -c '%i %h %n' /mnt/data/torrents/tv/*/*.mkv
stat -c '%i %h %n' /mnt/data/media/Shows/*/*/*.mkvالرقم الأول هو inode، والثاني هو عدد الروابط. يظهر الملف الذي أُنشئ له رابط صلب باستخدام inode نفسه في كلا المكانين، ويكون عدد الروابط 2. أما ظهور inode مختلف في كل مكان، مع عدد روابط 1 لكل منهما، فيعني أن Sonarr نسخ الملف، وسيذكر سجل الاستيراد فشل إنشاء الرابط الصلب.
راقب القرص أيضاً. يجب ألا يتغير df -h /mnt/data تقريباً عند إجراء الاستيراد، لأن الرابط الصلب يضيف اسماً ولا يضيف بيانات.
ما الذي يتعطل فعلياً
تعني أخطاء الصلاحيات أثناء الاستيراد أن معرّف مستخدم الحاوية لا يستطيع الكتابة في مجلد المكتبة. الرسالة هي Access to the path ... is denied. تحقّق باستخدام ls -ln /mnt/data/media من أن معرّف المالك يطابق PUID، وتذكّر أن المجلدات تحتاج إلى بت التنفيذ قبل أن تتمكن الحاوية من الدخول إليها.
تعني الملفات التي يظهر أن مالكها هو root أن الحاوية بدأت قبل وجود مجلد المضيف، لذلك أنشأه Docker بملكية root. أوقف المكدس، chown المجلد، ثم شغّله مرة أخرى.
إذا حذفت torrent من qBittorrent ثم وجدت أن ملف المكتبة اختفى، فهذا يعني أن الاستيراد كان عملية نسخ حُذفت لاحقاً، أو أنك حذفت البيانات بدلاً من إدخال torrent. في حالة hardlink حقيقي، يؤدي حذف أحد الاسمين إلى إبقاء الاسم الآخر سليماً، لأن البيانات لا تُحرَّر إلا عندما يصل عدد الروابط إلى الصفر.
امتلاء القرص بسرعة أكبر من حجم الوسائط التي أضفتها هو أكثر أشكال مشكلة النسخ تكلفة. شغّل فحص stat أعلاه قبل شراء مساحة تخزين إضافية.
ما يحتاجه هذا المكدس من VPS
تطبيقات arr الثلاثة خفيفة. فهي تستعلم من indexers، وتكتب في قاعدة بيانات SQLite صغيرة، وتعيد تسمية الملفات. يشغّل خادم بذاكرة RAM سعتها 2 GB الحاويات الأربع بسهولة. يأتي الحمل من أماكن أخرى. يستهلك download client كامل قدرة الإدخال والإخراج على القرص عند التعامل مع ملفات torrent كبيرة، كما سيستهلك media server الذي يرمّز الفيديو تحويلًا CPU على الخادم نفسه. ضع الوسائط على volume ذي معدل نقل فعلي، واضبط حداً للنطاق الترددي في download client إذا كان الخادم ينفّذ مهام أخرى مهمة لك. خصّص موارد لهذه المهام الأخرى بشكل منفصل، ولا تفترض توفر هامش كافٍ: مساحة عمل AFFiNE مستضافة ذاتياً تتكون من أربع حاويات أخرى مع قاعدة بيانات خلفها، وعلى خادم بذاكرة 2 GB ستستهلك معظم الذاكرة لنفسها. لا تستهلك كل خدمة إضافية هذه الموارد: خدمة أحادية الغرض مثل متعقّب تمارين openGym مستضاف ذاتياً تعمل جيداً على الخادم نفسه، ما دمت تمنحها TLS خاصاً بها وتعرف مكان ملف قاعدة بياناتها قبل أن تعهد إليها بسجل تدريبات يمتد عاماً كاملاً. أما أي خدمة تتضمن web application وقاعدة بيانات Postgres وقائمة انتظار background worker، فتقع أقرب إلى طرف AFFiNE من هذا النطاق. لذلك قرر ما إذا كان مكتب دعم Chatwoot مستضاف ذاتياً سينتمي إلى هذا الخادم أو إلى خادم مستقل قبل أن تكتشف الحد أثناء عملية import. وتحتاج أحمال العمل المتقطعة إلى حذر أكبر، لأن ذروة الحمل، لا متوسطه، هي التي تتعارض مع عملية import: إذا كنت تفكر في OneCLI مستضاف ذاتياً يمنح كل شخص agent معزولاً خاصاً به، فقارن أرقام sizing المنشورة لديه بما هو متاح فعلياً أثناء تشغيل qBittorrent بأقصى سرعة، لا بما يعرضه free -h على خادم خامل.
FAQ
لماذا ينسخ Sonarr الملفات بدلاً من إنشاء روابط صلبة لها؟
لأن المصدر والوجهة موجودان على نظامي ملفات مختلفين من منظور الحاوية. يُعدّ كل تركيب bind مستقل، مثل /downloads و/tv، نظام ملفات مختلفاً حتى عندما يأتي كلاهما من قرص واحد على المضيف. ركّب دليلاً أباً واحداً على أنه /data في كل حاوية، وضع التنزيلات والمكتبة بداخله، وبذلك يصبح إنشاء الرابط ممكناً. أكّد النتيجة باستخدام stat -c '%i %h %n' على كلا الملفين: يجب أن تكون قيمة inode واحدة، وأن يكون عدد الروابط 2.
ما PUID وPGID اللذان يجب أن أستخدمهما؟
استخدم المعرّف الرقمي لحساب المضيف الذي يملك شجرة الوسائط. تحصل عليه باستخدام id -u وid -g. في Ubuntu VPS جديد، تكون القيمة عادةً 1000 لكليهما. يجب أن تستخدم كل حاوية في المكدس الزوج نفسه، وإلا فسيكتب أحد التطبيقات ملفات لا يستطيع تطبيق آخر تعديلها. بعد تغيير القيم، أعد إنشاء الحاويات باستخدام docker compose up -d --force-recreate، وأصلح الملفات الموجودة باستخدام chown -R.
هل أحتاج إلى إتاحة واجهات الويب هذه على الإنترنت؟
لا، ولا ينبغي لك ذلك. اربط كل منفذ منشور بالعنوان 127.0.0.1 في ملف Compose، ثم ادخل إلى الواجهات عبر نفق SSH أو عبر Reverse Proxy على الخادم، بحيث ينهي TLS (أمان طبقة النقل) ويضيف المصادقة الخاصة به. لا يستطيع VPN وحده الوصول إلى منفذ مرتبط بالعنوان 127.0.0.1، لأن Docker يجعله متاحاً من المضيف نفسه فقط. إذا أردت الوصول المباشر عبر VPN، فانشر المنافذ على عنوان VPN الخاص بالخادم بدلاً من ذلك. نشرها مباشرة على الواجهة العامة أسوأ مما يبدو، لأن Docker يضيف قواعد جدار ناري خاصة به، ولن تمنع قاعدة deny في ufw حركة المرور هذه.
أين أجد كلمة مرور qBittorrent؟
تعرض صورة LinuxServer.io كلمة مرور مؤقتة للمستخدم admin في سجل بدء التشغيل. شغّل docker compose logs qbittorrent | grep -i password لقراءتها، ثم عيّن كلمة مرور دائمة ضمن Options وWeb UI. تُنشأ كلمة مرور مؤقتة جديدة عند كل إعادة تشغيل إلى أن تعيّن كلمة مرورك الخاصة.
هل يستطيع Jellyfin استخدام المجلدات نفسها؟
نعم، وهذا هو الهدف من هذا التخطيط. ركّب /mnt/data/media داخل خادم الوسائط على أنه /media، وستوجد مكتباته في /media/Movies و/media/Shows، بينما يكتب Sonarr وRadarr في الدلائل نفسها عبر /data/media. امنح خادم الوسائط PUID وPGID نفسيهما، حتى يتمكن من قراءة ما يكتبه مكدس arr.