SSD Nodes Learn Hosting plans →
الأدلة Matt Connorبقلم Matt Connor · آخر تحديث في 2026-08-28

ملف 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، ويجب أن يكون قابلاً للوصول لاتصالات الأقران الواردة. اسمح به باستخدام sudo ufw allow 6881، واقرأ أساسيات جدار ufw الناري على VPS إذا كان هذا الأمر جديداً عليك.

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

mkdir -p ~/arr/config/prowlarr ~/arr/config/sonarr ~/arr/config/radarr ~/arr/config/qbittorrent
docker compose up -d
docker compose ps

يجب أن تقرأ الخدمات الأربع جميعها running. اعتباراً من July 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 تستضيفها بنفسك. لا ينبغي أن تكون أي من هذه التطبيقات على الإنترنت العام مع صفحة تسجيل الدخول الخاصة بها وحدها كوسيلة حماية. إذا اخترت استخدام 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 الملفات بدلاً من إنشاء روابط صلبة لها؟

لأن المصدر والوجهة موجودان في نظامي ملفات مختلفين من منظور الحاوية. يُعدّ ربطا mount منفصلان، مثل /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 أو VPN أو Reverse Proxy ينهي TLS (أمان طبقة النقل) ويضيف آلية المصادقة الخاصة به. إن نشر هذه الواجهات مباشرة أسوأ مما يبدو، لأن 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.