Docker Compose برای Prowlarr، Sonarr، Radarr و qBittorrent
یک فایل Docker Compose برای اجرای Prowlarr، Sonarr، Radarr و qBittorrent روی VPS، با PUID، PGID و چیدمان volume مشترک برای حفظ hardlinkها.
آنچه میسازید
یک stack مبتنی بر Docker Compose شامل چهار container است که یک کتابخانه رسانهای را مدیریت میکنند: Prowlarr برای تنظیمات indexer، Sonarr برای سریالها، Radarr برای فیلمها و qBittorrent بهعنوان client دانلود. این containerها از طریق شبکه Compose و با استفاده از نام service با یکدیگر ارتباط برقرار میکنند و یک درخت پوشه مشترک روی host دارند. نصب آن کوتاه است. عاملی که تعیین میکند این stack سالها بدون مشکل کار کند یا هر هفته برای شما مشکل ایجاد کند، چیدمان volumeها است؛ بنابراین بیشتر این راهنما به همین موضوع اختصاص دارد.
این stack محتوا را برای شما پیدا نمیکند. Prowlarr هر indexerی را که به آن اضافه میکنید نگهداری میکند و انتخاب indexerها بر عهده شما و مسئولیت قانونی خودتان است. این راهنما بخش زیرساخت را پوشش میدهد: userها، pathها، permissionها، شبکهسازی containerها و بررسیهایی که عملکرد صحیح را تأیید میکنند.
اگر تاکنون فایل Compose ننوشتهاید، ابتدا مبانی Docker Compose برای یک VPS را بخوانید. این مطلب فرض میکند docker compose version از قبل روی server شما خروجی نمایش میدهد.
چرا hardlinkها از کار میافتند و چرا همهچیز به همین وابسته است
وقتی Sonarr پردازش یک download را تمام میکند، فایل را به library وارد میکند. اگر پوشه download و پوشه library روی یک filesystem باشند، این import بهصورت hardlink انجام میشود: نام دومی که به همان داده روی دیسک اشاره میکند. فضای اضافی یا زمان اضافی مصرف نمیشود. torrent با نام قبلی به seeding ادامه میدهد و media server نام جدید را میخواند.
اگر این دو پوشه روی filesystemهای متفاوت باشند، kernel نمیتواند این link را ایجاد کند. Sonarr بهجای آن فایل را copy میکند. اکنون یک فصل 40 GB به 80 GB فضای دیسک و چند دقیقه input و output نیاز دارد و import log ثبت میکند که hardlink شکست خورده و فایل بهجای آن copy شده است. در یک VPS با disk allowance ثابت، به این ترتیب ممکن است افراد ظرف یک هفته فضای دیسک خود را از دست بدهند.
مشکل اصلی اینجاست. در یک container، bind mount مرز یک filesystem است. اگر /mnt/data/torrents را بهصورت /downloads و /mnt/data/media را بهصورت /tv mount کنید، با اینکه هر دو روی یک دیسک میزبان قرار دارند، Sonarr آنها را دو mount جداگانه میبیند و از ایجاد link بین آنها خودداری میکند. مستندات رسمی image مربوط به LinuxServer.io مستقیماً به این موضوع اشاره میکند: استفاده از مسیرهای جداگانه /downloads و /tv امکان hardlink را از بین میبرد.
راهحل، استفاده از یک mount است. هر container که با media کار میکند، همان volume واحد، یعنی /mnt/data:/data، را دریافت میکند و هر مسیری که استفاده میکند، پوشهای داخل آن است. یک mount point، یک filesystem و hardlinkهای فعال.
ایجاد کاربر، گروه و پوشهها
کانتینرها فایلها را با یک شناسه کاربر عددی مینویسند که با PUID و PGID تعیین میشود. از حساب کاربری خودتان استفاده کنید تا بتوانید این فایلها را از طریق SSH، بدون sudo، بخوانید و ویرایش کنید.
id -u
id -gهر دو معمولاً در یک Ubuntu VPS تازه، 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 mount کنید تا کتابخانههای آن در /media/Movies و /media/Shows قرار بگیرند؛ دقیقاً همانجا که آن راهنما مشخص میکند.
فایل محیط
مقادیر متغیر در هر سرور را در .env، در کنار فایل Compose، نگه دارید.
mkdir -p ~/arr && cd ~/arr~/arr/.env را بنویسید:
PUID=1000
PGID=1000
TZ=Etc/UTC
DATA_ROOT=/mnt/dataTZ را روی منطقه زمانی خود، مانند 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 است و برای اتصالهای ورودی peer باید قابل دسترسی باشد. آن را با 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، این imageها در lscr.io منتشر میشوند و برچسب latest از stable release فعلی پیروی میکند؛ بنابراین اگر میخواهید ارتقاها تصمیمی آگاهانه باشند، نه یک غافلگیری، بهجای آن یک version tag مشخص کنید.
دسترسی ایمن به رابطهای وب
از آنجا که پورتها روی 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 که خودتان میزبانی میکنید به سرور متصل شوید. هیچیک از این برنامهها نباید فقط با صفحه ورود خود در اینترنت عمومی قرار بگیرند.
qBittorrent در اولین راهاندازی یک گذرواژه تصادفی برای مدیر تولید میکند و آن را در log کانتینر چاپ میکند. آن را بخوانید، سپس در رابط وب تغییر دهید:
docker compose logs qbittorrent | grep -i passwordاگر این گذرواژه را تغییر ندهید، در هر restart یک گذرواژه تصادفی جدید تولید میشود و هر بار دوباره باید به logها مراجعه کنید.
مسیرها را در هر برنامه تنظیم کنید
در qBittorrent، بخش Options و سپس Downloads را باز کنید و مسیر پیشفرض ذخیرهسازی را روی /data/torrents تنظیم کنید. پوشه دانلودهای ناقص را نیز در همان درخت نگه دارید؛ برای مثال، /data/torrents/incomplete. دانلودی که در خارج از /data تکمیل شود، نمیتواند با hard link در کتابخانه قرار گیرد.
در Sonarr، بخش Settings و سپس Media Management را باز کنید و پوشه ریشه /data/media/Shows را اضافه کنید. پوشه ریشه در Radarr، /data/media/Movies است. این مسیرها داخل container هستند. مسیر میزبان، یعنی /mnt/data/media/Shows، رد میشود، زیرا این پوشه از دید container وجود ندارد.
در Sonarr و Radarr، بخش Settings و سپس Download Clients را باز کنید و qBittorrent را اضافه کنید. میزبان qbittorrent و پورت 8080 است. نام سرویس بهعنوان نام میزبان کار میکند، زیرا Compose هر چهار container را در یک شبکه، همراه با سرویس داخلی DNS (domain name system)، قرار میدهد. در این بخش از localhost استفاده نکنید: درون container مربوط به Sonarr، localhost همان Sonarr است.
Remote Path Mappings را خالی بگذارید. این قابلیت برای تبدیل مسیری استفاده میشود که download client گزارش میکند به مسیری که برنامه arr بتواند ببیند. با یک mount مشترک /data، هر دو container از قبل درباره همه مسیرها توافق دارند؛ این دومین دلیل ارزشمند بودن این چیدمان است.
اتصال Prowlarr به Sonarr و Radarr
Prowlarr تعریفهای indexer را به برنامههای دیگر ارسال میکند. بنابراین indexer را فقط یکبار پیکربندی میکنید. Prowlarr برای هر برنامه به یک کلید API (رابط برنامهنویسی کاربردی) نیاز دارد.
در 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 نیز تکرار کنید.
نتیجه قرمز با پیام رد شدن اتصال تقریباً همیشه بهدلیل نام نادرست سرویس یا نبود پیشوند http:// است. بررسی کنید که نام سرویس از داخل کانتینر resolve میشود:
docker compose exec prowlarr curl -sS -o /dev/null -w '%{http_code}\n' http://sonarr:8989کد وضعیت HTTP ثابت میکند که مسیر شبکه درست است. خطای resolution نام نشان میدهد که نام سرویس نادرست است.
اثبات اینکه hardlinkها واقعاً ایجاد میشوند
تا زمانی که تعداد linkها را ندیدهاید، به تنظیمات اعتماد نکنید. پس از import شدن یک مورد، فایل دانلودشده را با فایل موجود در library مقایسه کنید:
stat -c '%i %h %n' /mnt/data/torrents/tv/*/*.mkv
stat -c '%i %h %n' /mnt/data/media/Shows/*/*/*.mkvعدد اول inode و عدد دوم تعداد linkها است. فایلی که hardlink شده باشد، در هر دو مسیر inode یکسان و تعداد link برابر با 2 دارد. دو inode متفاوت که هرکدام تعداد link برابر با 1 دارند، یعنی Sonarr فایل را کپی کرده است و import log اعلام میکند که hardlink ناموفق بوده است.
فضای دیسک را نیز بررسی کنید. هنگام import، مقدار df -h /mnt/data باید تقریباً بدون تغییر بماند، زیرا hardlink فقط یک نام اضافه میکند و دادهای اضافه نمیکند.
آنچه واقعاً خراب میشود
خطاهای مجوز هنگام import به این معنا هستند که شناسه کاربر کانتینر نمیتواند در پوشه library بنویسد. پیام خطا Access to the path ... is denied است. با ls -ln /mnt/data/media بررسی کنید که شناسه مالک با PUID مطابقت داشته باشد. همچنین به یاد داشته باشید که پوشهها پیش از ورود کانتینر به آنها، به مجوز execute نیاز دارند.
فایلهایی که ظاهراً مالک آنها root است، نشان میدهند که کانتینر پیش از ایجاد پوشه در host راهاندازی شده است؛ بنابراین Docker آن را با مالکیت root ایجاد کرده است. stack را متوقف کنید، پوشه را chown کنید و سپس آن را دوباره راهاندازی کنید.
اگر torrent را از qBittorrent حذف کنید و فایل library نیز ناپدید شود، import بهصورت copy انجام شده و فایل بعداً حذف شده است، یا بهجای ورودی torrent، دادهها را حذف کردهاید. در یک hardlink واقعی، حذف یک نام باعث سالم ماندن نام دیگر میشود، زیرا داده فقط زمانی آزاد میشود که تعداد linkها به صفر برسد.
پر شدن دیسک با سرعتی بیشتر از حجم media اضافهشده، پرهزینهترین شکل مشکل copy است. پیش از خرید storage بیشتر، بررسی stat بالا را اجرا کنید.
این stack از یک VPS چه نیاز دارد
سه برنامه arr سبک هستند. آنها indexerها را بررسی میکنند، در یک پایگاهداده کوچک SQLite مینویسند و نام فایلها را تغییر میدهند. یک سرور با 2 GB RAM هر چهار container را بهراحتی اجرا میکند. بار اصلی از بخش دیگری میآید. یک download client هنگام دریافت torrentهای بزرگ، ورودی و خروجی دیسک را اشباع میکند و یک media server که روی همان سیستم ویدئو را transcode میکند، CPU را درگیر خواهد کرد. فایلهای media را روی یک volume با throughput واقعی نگه دارید و اگر سرور وظایف مهم دیگری دارد، برای download client محدودیت bandwidth تعیین کنید.
FAQ
چرا Sonarr بهجای ایجاد hardlink، فایلها را کپی میکند؟
زیرا از دیدگاه container، مبدأ و مقصد روی filesystemهای متفاوت قرار دارند. دو bind mount جداگانه، مانند /downloads و /tv، حتی اگر هر دو از یک دیسک میزبان تأمین شوند، دو filesystem محسوب میشوند. یک دایرکتوری والد واحد را در همه containerها بهصورت /data mount کنید و downloads و library را داخل آن قرار دهید تا ایجاد link ممکن شود. نتیجه را با stat -c '%i %h %n' روی هر دو فایل بررسی کنید: inode یکسان و link count برابر با 2 باشد.
از کدام PUID و PGID باید استفاده کنم؟
از id عددی حساب کاربری میزبان که مالک media tree است استفاده کنید. این مقدار را با id -u و id -g بهدست میآورید. در یک Ubuntu VPS تازه، این مقدار معمولاً برای هر دو مورد 1000 است. همه containerهای این stack باید از همین جفت مقدار استفاده کنند؛ در غیر این صورت، یک application فایلهایی ایجاد میکند که application دیگری نمیتواند آنها را تغییر دهد. پس از تغییر مقادیر، containerها را با docker compose up -d --force-recreate دوباره ایجاد کنید و فایلهای موجود را با chown -R اصلاح کنید.
آیا لازم است این رابطهای وب را در اینترنت در دسترس قرار دهم؟
خیر، و نباید این کار را انجام دهید. هر port منتشرشده را در فایل Compose به 127.0.0.1 bind کنید، سپس از طریق SSH tunnel، VPN یا reverse proxy که TLS (امنیت لایه انتقال) را خاتمه میدهد و احراز هویت مستقل خود را اضافه میکند، به رابطها دسترسی پیدا کنید. انتشار مستقیم این رابطها خطرناکتر از چیزی است که به نظر میرسد، زیرا Docker قوانین firewall خود را اضافه میکند و یک قانون deny در ufw جلوی این ترافیک را نمیگیرد.
گذرواژه qBittorrent را از کجا پیدا کنم؟
image مربوط به LinuxServer.io یک گذرواژه موقت برای کاربر admin را در startup log چاپ میکند. برای خواندن آن، docker compose logs qbittorrent | grep -i password را اجرا کنید، سپس در بخش Options و Web UI یک گذرواژه دائمی تعیین کنید. تا زمانی که گذرواژه خودتان را تعیین نکنید، با هر restart یک گذرواژه موقت جدید ایجاد میشود.
آیا Jellyfin میتواند از همان پوشهها استفاده کند؟
بله، هدف این layout همین است. /mnt/data/media را در media server خود بهصورت /media mount کنید. در این حالت، libraryهای آن در /media/Movies و /media/Shows قرار میگیرند و Sonarr و Radarr از طریق /data/media در همان دایرکتوریها مینویسند. همان PUID و PGID را به media server اختصاص دهید تا بتواند فایلهایی را که arr stack ایجاد میکند بخواند.