راهنمای راهاندازی استک Arr در یک فایل Docker Compose
با استفاده از یک فایل Docker Compose، سرویسهای Prowlarr، Sonarr، Radarr و qBittorrent را روی VPS اجرا کنید. تنظیمات صحیح PUID، PGID و Volume برای عملکرد هاردلینکها ضروری است.
آنچه در حال ساخت آن هستید
یک استک arr در Docker Compose شامل چهار کانتینر است که یک کتابخانه رسانهای را مدیریت میکنند: Prowlarr برای تنظیمات ایندکسرها، Sonarr برای سریالها، Radarr برای فیلمها و qBittorrent به عنوان کلاینت دانلود. این سرویسها از طریق نام سرویس در شبکه Compose با یکدیگر ارتباط برقرار میکنند و یک ساختار درختی از پوشهها را روی میزبان (host) به اشتراک میگذارند. نصب این مجموعه کوتاه است. بخشی که تعیین میکند آیا این استک سالها بدون مشکل کار میکند یا هر هفته برای شما دردسر ایجاد میکند، نحوه چیدمان volumeها است؛ بنابراین بخش عمدهای از این راهنما به همین موضوع اختصاص دارد.
این استک محتوا را برای شما پیدا نمیکند. Prowlarr تنها ایندکسرهایی را که خودتان به آن اضافه میکنید نگه میدارد و انتخاب ایندکسرها و مسئولیت قانونی استفاده از آنها بر عهده شماست. این راهنما به زیرساختها میپردازد: کاربران، مسیرها، مجوزها، شبکهبندی کانتینرها و بررسیهایی که صحت عملکرد سیستم را تأیید میکنند.
اگر تا به حال فایل Compose ننوشتهاید، ابتدا مبانی Docker Compose برای VPS را مطالعه کنید. این مطلب فرض را بر این میگذارد که docker compose version هماکنون خروجی معتبری در سرور شما نمایش میدهد.
چرا هاردلینکها از کار میافتند و چرا این موضوع اهمیت حیاتی دارد
وقتی Sonarr کار دانلود را به پایان میرساند، فایل را به کتابخانه شما منتقل (import) میکند. اگر پوشه دانلود و پوشه کتابخانه روی یک فایلسیستم یکسان باشند، این انتقال به صورت یک هاردلینک انجام میشود: نام دومی که به همان دادههای روی دیسک اشاره میکند. این کار هیچ فضای اضافی اشغال نمیکند و زمانبر نیست. تورنت همچنان از نام قدیمی به seeding ادامه میدهد، در حالی که مدیا سرور شما فایل را از نام جدید میخواند.
اگر این دو پوشه روی فایلسیستمهای متفاوتی باشند، کرنل نمیتواند آن لینک را ایجاد کند. در این حالت Sonarr به کپی کردن فایل روی میآورد. یک فصل سریال با حجم 40 GB حالا 80 GB از دیسک را اشغال میکند و چندین دقیقه عملیات ورودی و خروجی (I/O) صرف آن میشود؛ لاگ انتقال نیز ثبت میکند که هاردلینک ناموفق بوده و فایل به جای آن کپی شده است. در یک VPS با فضای دیسک محدود، این همان دلیلی است که باعث میشود کاربران در عرض یک هفته با کمبود فضا مواجه شوند.
تله اصلی اینجاست. داخل یک کانتینر، یک bind mount یک مرز فایلسیستمی محسوب میشود. اگر /mnt/data/torrents را به عنوان /downloads و /mnt/data/media را به عنوان /tv مانت کنید، با وجود اینکه هر دو روی یک دیسک میزبان قرار دارند، Sonarr آنها را دو مانت مجزا میبیند و از ایجاد لینک بین آنها خودداری میکند. مستندات رسمی ایمیج LinuxServer.io مستقیماً به این موضوع اشاره دارد: استفاده از مسیرهای جداگانه /downloads و /tv باعث از دست رفتن قابلیت هاردلینک میشود.
راه حل، استفاده از یک مانت واحد است. هر کانتینری که با فایلهای مدیا سروکار دارد باید همان volume واحد یعنی /mnt/data:/data را دریافت کند و تمام مسیرهایی که استفاده میکنند، پوشههایی در دل همان volume باشند. یک نقطه مانت، یک فایلسیستم، و هاردلینکهای فعال.
ایجاد کاربر، گروه و پوشهها
کانتینرها فایلها را با یک شناسه کاربری عددی مینویسند که توسط PUID و PGID تعیین میشود. از حساب کاربری خود استفاده کنید تا بتوانید بدون نیاز به sudo، آن فایلها را از طریق SSH بخوانید و ویرایش کنید.
id -u
id -gهر دو معمولاً در یک VPS تازه با سیستمعامل Ubuntu مقدار 1000 را نمایش میدهند. اکنون ساختار درختی را ایجاد کنید. آن را روی هر دیسکی که رسانههای شما را در خود جای داده است قرار دهید و کل این ساختار را روی همان یک دیسک نگه دارید.
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هر دو خط باید دستگاه منبع یکسانی را نشان دهند. وجود دو دستگاه متفاوت به این معنی است که hardlinkها هرگز کار نخواهند کرد، فارغ از اینکه در پیکربندی کانتینر چه چیزی تنظیم کرده باشید.
پوشههای کتابخانه به عمد Movies و Shows نامگذاری شدهاند. اگر قبلاً Jellyfin را به عنوان سرور رسانه خود اجرا کردهاید، /mnt/data/media را در Jellyfin به عنوان /media مونت کنید تا کتابخانههای آن دقیقاً در /media/Movies و /media/Shows قرار بگیرند، یعنی همانجایی که آن راهنما تعیین کرده است.
فایل محیطی (Environment file)
مقادیری را که در هر سرور تغییر میکنند، در .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 عمداً روی تمام رابطها منتشر میشود. این پورتِ گوشدهی تورنت است و باید برای اتصالات ورودی همتایان (peers) در دسترس باشد. آن را با 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 را بخوانند. از ژوئیه 2026، این ایمیجها در lscr.io منتشر میشوند و تگ latest نسخه پایدار فعلی را دنبال میکند؛ بنابراین اگر میخواهید ارتقاها یک تصمیم آگاهانه باشند و نه یک غافلگیری، از یک تگ نسخه ثابت (pin) استفاده کنید.
دسترسی ایمن به رابطهای وب
از آنجا که پورتها روی 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 روی سرور دسترسی پیدا میکند. برای دسترسی دائمی، این stack را پشت Traefik با گواهیهای TLS برای چندین برنامه قرار دهید، یا از طریق یک VPN WireGuard که خودتان میزبانی میکنید به سرور متصل شوید. هیچکدام از این برنامهها نباید تنها با تکیه بر صفحه ورود خودشان، مستقیماً روی اینترنت عمومی قرار بگیرند. اگر مسیر reverse proxy را انتخاب کردید و ترجیح میدهید بهجای مدیریت چهار حساب کاربری جداگانه، یک حساب واحد برای هر چهار رابط داشته باشید، Authentik قابلیت single sign-on خودمیزبان را ارائه میدهد که Traefik میتواند آن را با استفاده از forward auth روی تمامی درخواستها اعمال کند.
برنامه qBittorrent در اولین اجرا یک رمز عبور تصادفی برای مدیر تولید کرده و آن را در لاگ container چاپ میکند. آن را بخوانید و سپس در رابط وب تغییر دهید:
docker compose logs qbittorrent | grep -i passwordاگر این تغییر را انجام ندهید، با هر بار راهاندازی مجدد، یک رمز عبور تصادفی جدید تولید میشود و هر بار مجبور خواهید بود به لاگها مراجعه کنید.
تنظیم مسیرها در هر برنامه
در qBittorrent، به بخش Options و سپس Downloads بروید و مسیر پیشفرض ذخیرهسازی را روی /data/torrents تنظیم کنید. پوشه دانلودهای ناقص (incomplete-downloads) را در همان شاخه نگه دارید، مثلاً در /data/torrents/incomplete. دانلودی که در جایی خارج از /data به پایان برسد، نمیتواند به صورت hardlink در کتابخانه قرار گیرد.
در Sonarr، به بخش Settings و سپس Media Management بروید و پوشه ریشه (root folder) را به صورت /data/media/Shows اضافه کنید. در Radarr، پوشه ریشه /data/media/Movies است. اینها مسیرهایی در داخل container هستند. مسیر میزبان (host path) یعنی /mnt/data/media/Shows رد میشود، زیرا آن دایرکتوری از دیدگاه container وجود ندارد.
در هر دو برنامه Sonarr و Radarr، به بخش Settings و سپس Download Clients بروید و qBittorrent را اضافه کنید. میزبان (host) برابر با qbittorrent و پورت برابر با 8080 است. نام سرویس به عنوان hostname عمل میکند، زیرا Compose هر چهار container را در یک شبکه با سرویس DNS (سیستم نام دامنه) داخلی قرار میدهد. در اینجا از localhost استفاده نکنید: در داخل container مربوط به Sonarr، عبارت localhost همان Sonarr است.
بخش Remote Path Mappings را خالی بگذارید. این قابلیت برای ترجمه مسیری که کلاینت دانلود گزارش میدهد به مسیری که برنامه arr میتواند ببیند، وجود دارد. با داشتن یک mount مشترک در /data، هر دو container از قبل روی تمام مسیرها توافق دارند؛ این دومین دلیلی است که این ساختار ارزش صرف وقت را دارد.
اتصال Prowlarr به Sonarr و Radarr
نرمافزار Prowlarr تعاریف ایندکسرها را به سایر برنامهها ارسال میکند، بنابراین شما به جای دو بار، فقط یک بار ایندکسر را پیکربندی میکنید. این کار نیازمند یک API (رابط برنامهنویسی اپلیکیشن) key از هر برنامه است.
در Sonarr، بخش Settings و سپس General را باز کرده و API key را کپی کنید. در Prowlarr، بخش Settings و سپس Apps را باز کنید، یک اپلیکیشن Sonarr اضافه کرده و سه فیلد را پر کنید. Prowlarr Server برابر با http://prowlarr:9696 است. Sonarr Server برابر با http://sonarr:8989 است. API Key همان مقداری است که کپی کردید. دکمه Test را بزنید. نتیجه سبز به این معنی است که Prowlarr از طریق شبکه Compose به Sonarr دسترسی پیدا کرده است. این مراحل را برای Radarr در http://radarr:7878 تکرار کنید.
نتیجه قرمز با پیام connection was refused تقریباً همیشه به معنای نام سرویس اشتباه یا نبود پیشوند http:// است. بررسی کنید که نام سرویس از داخل کانتینر resolve میشود یا خیر:
docker compose exec prowlarr curl -sS -o /dev/null -w '%{http_code}\n' http://sonarr:8989یک کد وضعیت HTTP ثابت میکند که مسیر شبکه مشکلی ندارد. خطای name resolution ثابت میکند که نام سرویس اشتباه است.
اثبات عملکرد صحیح hardlink
تا زمانی که تعداد لینکها (link count) را مشاهده نکردهاید، به تنظیمات اعتماد نکنید. پس از وارد کردن یک آیتم، فایل دانلود شده را با فایل موجود در کتابخانه مقایسه کنید:
stat -c '%i %h %n' /mnt/data/torrents/tv/*/*.mkv
stat -c '%i %h %n' /mnt/data/media/Shows/*/*/*.mkvعدد اول inode و عدد دوم تعداد لینکها است. فایلی که hardlink شده باشد، inode یکسانی را در هر دو مکان نشان میدهد و تعداد لینک آن 2 است. وجود دو inode متفاوت که هر کدام تعداد لینک 1 دارند، به این معنی است که Sonarr فایل را کپی کرده است و لاگ وارد کردن (import log) نیز عدم موفقیت در ایجاد hardlink را گزارش خواهد کرد.
همچنین وضعیت دیسک را زیر نظر بگیرید. هنگام انجام عملیات import، مقدار df -h /mnt/data نباید تغییر محسوسی داشته باشد، زیرا hardlink فقط یک نام جدید اضافه میکند و هیچ دادهای را جابهجا نمیکند.
چه چیزی واقعاً باعث خرابی میشود
خطاهای مجوز در هنگام import به این معناست که شناسه کاربری (user id) کانتینر نمیتواند در پوشه library بنویسد. پیام خطا Access to the path ... is denied است. با دستور ls -ln /mnt/data/media بررسی کنید که شناسه مالک با PUID شما مطابقت داشته باشد و به یاد داشته باشید که دایرکتوریها پیش از آنکه کانتینر بتواند وارد آنها شود، به بیت execute نیاز دارند.
فایلهایی که به نظر میرسد مالک آنها root است، به این معناست که کانتینر پیش از ایجاد دایرکتوری در میزبان (host) شروع به کار کرده است، بنابراین Docker آن را با دسترسی root ایجاد کرده است. stack را متوقف کنید، دایرکتوری را chown کنید و دوباره آن را استارت بزنید.
حذف یک torrent از qBittorrent و مشاهده اینکه فایل library از بین رفته است، به این معناست که import به صورت کپی انجام شده و بعداً حذف شده است، یا اینکه شما به جای حذف ورودی torrent، دادهها را پاک کردهاید. با استفاده از hardlink واقعی، حذف یک نام، نام دیگر را دستنخورده باقی میگذارد، زیرا دادهها تنها زمانی آزاد میشوند که تعداد لینکها به صفر برسد.
پر شدن دیسک با سرعتی بیش از رسانهای که اضافه کردهاید، همان مشکل کپی در گرانترین حالت خود است. پیش از خرید فضای ذخیرهسازی بیشتر، بررسی stat ذکر شده در بالا را اجرا کنید.
نیازهای این پشته نرمافزاری از یک VPS
سه برنامهٔ arr سبک هستند. آنها indexerها را poll میکنند، در یک پایگاهدادهٔ کوچک SQLite مینویسند و نام فایلها را تغییر میدهند. سروری با 2 GB RAM هر چهار container را بدون مشکل اجرا میکند. بار اصلی از بخشهای دیگری میآید. یک download client هنگام دریافت torrentهای بزرگ، ورودی و خروجی دیسک را اشباع میکند و media server که روی همان سرور video را transcode میکند، CPU را درگیر خواهد کرد. فایلهای media را روی volumeای با throughput واقعی نگه دارید و اگر سرور کار مهم دیگری انجام میدهد، برای download client محدودیت bandwidth تعیین کنید. برای این موارد جداگانه منابع در نظر بگیرید و فرض نکنید ظرفیت اضافی همیشه وجود دارد: یک workspace خودمیزبان AFFiNE چهار container دیگر بههمراه یک database در پشت خود دارد و روی سروری با 2 GB، بیشتر حافظه را به خودش اختصاص میدهد. هر سرویس اضافی چنین هزینهای ندارد: چیزی تکمنظوره مانند یک workout tracker خودمیزبان openGym بهخوبی منابع سرور را به اشتراک میگذارد، بهشرط آنکه TLS اختصاصی خود را برای آن فراهم کنید و پیش از سپردن سابقهٔ یک سال تمرین به آن، بدانید فایل database آن کجا قرار دارد. هر چیزی که یک web application، یک Postgres database و یک background worker queue داشته باشد، به انتهای بازهٔ مربوط به AFFiNE نزدیکتر است؛ بنابراین پیش از آنکه محدودیت را در میانهٔ import کشف کنید، تصمیم بگیرید که یک support desk خودمیزبان Chatwoot باید روی همین سرور باشد یا روی سروری جداگانه. دربارهٔ workloadهای bursty باید محتاطتر باشید، زیرا هنگام import، این peak workload است، نه میانگین آن، که باعث رقابت بر سر منابع میشود: اگر در حال بررسی یک OneCLI خودمیزبان هستید که به هر شخص agent sandboxed اختصاصی میدهد، اعداد sizing منتشرشدهٔ آن را با مقدار واقعی منابع آزاد در زمانی که qBittorrent با حداکثر سرعت در حال اجراست مقایسه کنید، نه با مقداری که free -h روی سرور idle نشان میدهد.
FAQ
چرا Sonarr بهجای hardlink کردن، فایلها را کپی میکند؟
زیرا از دیدگاه container، مبدأ و مقصد روی دو فایلسیستم متفاوت قرار دارند. دو bind mount مجزا، مانند /downloads و /tv، حتی اگر هر دو از یک دیسک میزبان آمده باشند، دو فایلسیستم محسوب میشوند. یک دایرکتوری والد واحد را به عنوان /data در هر container مانت کنید و پوشههای downloads و library را درون آن قرار دهید تا امکان ایجاد لینک فراهم شود. نتیجه را با دستور stat -c '%i %h %n' روی هر دو فایل بررسی کنید: باید inode یکسان و تعداد لینک 2 را مشاهده کنید.
از چه PUID و PGID باید استفاده کنم؟
از شناسه عددی (numeric id) حساب کاربری میزبان که مالک درخت رسانهها (media tree) است استفاده کنید؛ این شناسه را میتوانید با دستورات id -u و id -g به دست آورید. در یک VPS تازه با سیستمعامل Ubuntu، این مقدار معمولاً برای هر دو برابر 1000 است. تمام containerها در این stack باید از جفت یکسانی استفاده کنند، در غیر این صورت یک برنامه فایلهایی مینویسد که برنامه دیگر قادر به تغییر آنها نیست. پس از تغییر مقادیر، containerها را با docker compose up -d --force-recreate بازسازی کنید و فایلهای موجود را با chown -R اصلاح نمایید.
آیا نیاز است این رابطهای وب را در معرض اینترنت قرار دهم؟
خیر، و نباید این کار را انجام دهید. هر پورت منتشرشده را در فایل Compose به 127.0.0.1 محدود کنید و سپس از طریق تونل SSH، VPN یا یک reverse proxy که TLS (امنیت لایه انتقال) را خاتمه میدهد و احراز هویت اختصاصی خود را اضافه میکند، به رابطها دسترسی پیدا کنید. انتشار مستقیم آنها بسیار خطرناکتر از آن چیزی است که به نظر میرسد، زیرا Docker قوانین فایروال خاص خود را تزریق میکند و یک قانون ufw deny نمیتواند جلوی آن ترافیک را بگیرد.
رمز عبور 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 را اختصاص دهید تا بتواند آنچه را که stack برنامههای arr مینویسد، بخواند.