ایک Docker Compose فائل میں مکمل arr stack
ایک VPS پر Prowlarr، Sonarr، Radarr اور qBittorrent چلائیں۔ مشترک PUID، PGID اور volume layout hardlinks برقرار رکھتے ہیں اور permissions کے مسائل کم کرتے ہیں۔
آپ کیا بنا رہے ہیں
Docker Compose پر مبنی arr stack چار containers پر مشتمل ہے جو media library manage کرتے ہیں: indexer settings کے لیے Prowlarr، series کے لیے Sonarr، films کے لیے Radarr، اور download client کے طور پر qBittorrent۔ یہ containers Compose network پر service name کے ذریعے ایک دوسرے سے رابطہ کرتے ہیں اور host پر ایک ہی folder tree share کرتے ہیں۔ installation مختصر ہے۔ stack کئی سال تک درست کام کرے گا یا ہر ہفتے مسائل پیدا کرے گا، اس کا فیصلہ volume layout کرتا ہے۔ اسی لیے اس guide کا زیادہ تر حصہ volume layout کے بارے میں ہے۔
stack آپ کے لیے content تلاش نہیں کرتا۔ Prowlarr میں وہی indexers موجود ہوتے ہیں جو آپ اس میں add کرتے ہیں۔ آپ کون سے indexers استعمال کرتے ہیں، یہ آپ کا فیصلہ اور قانونی ذمہ داری ہے۔ یہ guide بنیادی انتظامی امور کا احاطہ کرتی ہے: users، paths، permissions، container networking، اور وہ checks جو ثابت کرتے ہیں کہ stack درست کام کر رہا ہے۔
اگر آپ نے کبھی Compose file نہیں لکھی تو پہلے VPS کے لیے Docker Compose کی بنیادی باتیں پڑھیں۔ یہ post فرض کرتی ہے کہ docker compose version آپ کے server پر پہلے ہی کچھ output دکھاتا ہے۔
Hardlinks کیوں ٹوٹتے ہیں، اور اصل مسئلہ یہی کیوں ہے
جب Sonarr کسی download پر کارروائی مکمل کرتا ہے تو وہ file کو آپ کی library میں import کرتا ہے۔ اگر download folder اور library folder ایک ہی filesystem پر ہوں تو import ایک hardlink ہوتا ہے: disk پر موجود اسی data کی طرف اشارہ کرنے والا دوسرا نام۔ اس میں اضافی جگہ یا وقت نہیں لگتا۔ torrent پرانے نام سے seeding جاری رکھتا ہے، جبکہ media server نئے نام سے file پڑھتا ہے۔
اگر دونوں folders مختلف filesystems پر ہوں تو kernel یہ link نہیں بنا سکتا۔ Sonarr copy پر واپس آ جاتا ہے۔ اب 40 GB کا ایک season disk پر 80 GB جگہ اور input/output کے کئی منٹ لیتا ہے۔ import log میں درج ہوتا ہے کہ hardlink ناکام ہوا اور file کے بجائے copy بنائی گئی۔ مقررہ disk allowance والے VPS پر لوگ اسی طرح ایک ہفتے میں space ختم کر بیٹھتے ہیں۔
یہاں ایک اہم trap ہے۔ container کے اندر bind mount ایک filesystem boundary ہوتا ہے۔ /mnt/data/torrents کو /downloads اور /mnt/data/media کو /tv کے طور پر mount کریں تو دونوں ایک ہی host disk پر موجود ہونے کے باوجود Sonarr انہیں دو الگ mounts سمجھتا ہے اور ان کے درمیان link بنانے سے انکار کر دیتا ہے۔ LinuxServer.io کی official image documentation میں یہی بات براہ راست بیان کی گئی ہے: الگ /downloads اور /tv paths استعمال کرنے سے hardlink بنانے کی صلاحیت ختم ہو جاتی ہے۔
حل ایک mount ہے۔ media کو استعمال کرنے والے ہر container کو یہی واحد volume، /mnt/data:/data، دیں، اور ان کے استعمال کردہ تمام paths اسی کے اندر موجود folders ہوں۔ ایک mount point، ایک filesystem، اور قابلِ استعمال hardlinks۔
صارف، گروپ اور فولڈرز بنائیں
کنٹینرز فائلیں numeric user id کے طور پر لکھتے ہیں، جسے PUID اور PGID مقرر کرتے ہیں۔ اپنا اکاؤنٹ استعمال کریں تاکہ آپ sudo کے بغیر SSH کے ذریعے ان فائلوں کو پڑھ اور edit کر سکیں۔
id -u
id -gتازہ Ubuntu VPS پر دونوں عموماً 1000 دکھاتے ہیں۔ اب directory tree بنائیں۔ اسے اس disk پر رکھیں جس پر آپ کا media موجود ہے، اور پوری tree اسی ایک disk پر رکھیں۔
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آگے بڑھنے سے پہلے تصدیق کریں کہ یہ واقعی ایک ہی filesystem ہے:
df --output=source,target /mnt/data/torrents /mnt/data/mediaدونوں lines میں ایک ہی source device دکھائی دینا چاہیے۔ دو مختلف devices کا مطلب ہے کہ hardlinks کبھی کام نہیں کریں گے، چاہے آپ container config میں کچھ بھی set کریں۔
library folders کے نام جان بوجھ کر Movies اور Shows رکھے گئے ہیں۔ اگر آپ پہلے ہی Jellyfin کو اپنے media server کے طور پر چلا رہے ہیں تو /mnt/data/media کو Jellyfin میں /media کے طور پر mount کریں۔ اس طرح اس کی libraries /media/Movies اور /media/Shows میں پہنچیں گی، بالکل وہاں جہاں وہ guide انہیں رکھتی ہے۔
ماحولیاتی فائل
ہر سرور کے مطابق تبدیل ہونے والی قدریں Compose فائل کے ساتھ موجود .env میں رکھیں۔
mkdir -p ~/arr && cd ~/arr~/arr/.env لکھیں:
PUID=1000
PGID=1000
TZ=Etc/UTC
DATA_ROOT=/mnt/dataTZ کو اپنے zone پر سیٹ کریں، مثلاً Europe/Berlin۔ arr ایپلیکیشنز اسی zone میں tasks شیڈول کرتی ہیں اور log lines پر timestamp درج کرتی ہیں، اس لیے غلط value بعد میں ہر log کو مبہم بنا دیتی ہے۔
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 ان تین containers میں یکساں ہے جو media کے ساتھ کام کرتے ہیں۔ Prowlarr کو یہ نہیں ملتا، کیونکہ Prowlarr کبھی media file نہیں کھولتا۔
ہر web port کو 127.0.0.1 سے bind کیا گیا ہے، اس لیے Docker اسے صرف loopback address پر publish کرتا ہے۔ ایک سادہ 8989:8989 اسے ہر interface پر publish کر دیتا، اور Docker کے اپنے firewall rules اس traffic کو ufw کے deny rule سے براہ راست آگے پہنچا دیتے۔ یہ رویہ لوگوں کو مسلسل حیران کرتا ہے، اور Docker ufw کے راستے ports براہ راست کیوں publish کرتا ہے میں اس کی وضاحت کی گئی ہے۔
Port 6881 کو دانستہ طور پر تمام interfaces پر publish کیا گیا ہے۔ یہ torrent listening port ہے، اور incoming peer connections کے لیے اس تک رسائی ضروری ہے۔ اسے sudo ufw allow 6881 سے allow کریں، اور اگر یہ command نئی ہے تو VPS کے لیے ufw firewall کی بنیادی باتیں پڑھیں۔
Configuration directories ہر application کے لیے الگ ہیں، جبکہ صرف media volume مشترک ہے۔ پہلی بار start کرنے سے پہلے انہیں بنائیں تاکہ ان کی ملکیت root کے بجائے آپ کے user کے پاس ہو:
mkdir -p ~/arr/config/prowlarr ~/arr/config/sonarr ~/arr/config/radarr ~/arr/config/qbittorrent
docker compose up -d
docker compose psچاروں services کو running پڑھنا چاہیے۔ July 2026 تک یہ images lscr.io پر publish کی گئی ہیں اور latest tag موجودہ stable release کی پیروی کرتا ہے، اس لیے اگر آپ upgrades کو اچانک ہونے کے بجائے باقاعدہ فیصلے کے تحت رکھنا چاہتے ہیں تو version tag pin کریں۔
ویب انٹرفیسز تک محفوظ رسائی حاصل کریں
چونکہ ports صرف loopback پر دستیاب ہیں، اس لیے ابھی کچھ بھی exposed نہیں ہے۔ اپنی مشین سے انہیں SSH کے ذریعے forward کریں:
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اب آپ کے browser میں http://127.0.0.1:8989 سرور پر Sonarr تک پہنچتا ہے۔ مستقل رسائی کے لیے stack کو متعدد ایپس کے لیے TLS certificates والے Traefik کے پیچھے رکھیں، یا سرور تک اپنے زیر انتظام WireGuard VPN کے ذریعے پہنچیں۔ ان میں سے کسی بھی application کو public internet پر صرف اپنی login page کے ساتھ exposed نہیں ہونا چاہیے۔ اگر آپ reverse proxy کا طریقہ اختیار کرتے ہیں اور چار الگ application logins یاد رکھنے کے بجائے چاروں interfaces کے لیے ایک ہی account چاہتے ہیں، تو Authentik self-hosted single sign-on فراہم کرتا ہے جسے Traefik forward auth کے ذریعے ہر request پر نافذ کر سکتا ہے۔
qBittorrent پہلی بار start ہونے پر random administrator password بناتا ہے اور اسے container log میں لکھتا ہے۔ اسے پڑھیں، پھر web interface میں تبدیل کریں:
docker compose logs qbittorrent | grep -i passwordاگر آپ password تبدیل نہ کریں تو ہر restart پر نیا random password بنے گا، اور آپ کو ہر بار logs دیکھنے پڑیں گے۔
ہر application کے اندر paths متعین کریں
qBittorrent میں Options کھولیں، پھر Downloads پر جائیں، اور default save path کو /data/torrents پر set کریں۔ incomplete-downloads folder کو اسی tree کے اندر رکھیں، مثلاً /data/torrents/incomplete۔ /data سے باہر مکمل ہونے والا download library میں hardlink نہیں کیا جا سکتا۔
Sonarr میں Settings کھولیں، پھر Media Management پر جائیں، اور root folder /data/media/Shows شامل کریں۔ Radarr میں root folder /data/media/Movies ہے۔ یہ container کے اندر موجود paths ہیں۔ host path /mnt/data/media/Shows مسترد ہو جائے گا، کیونکہ container کے نقطۂ نظر سے یہ directory موجود نہیں ہے۔
Sonarr اور Radarr دونوں میں Settings کھولیں، پھر Download Clients پر جائیں، اور qBittorrent شامل کریں۔ host qbittorrent اور port 8080 ہے۔ service name hostname کے طور پر کام کرتا ہے، کیونکہ Compose تمام چار containers کو internal DNS (domain name system) service والے ایک network پر رکھتا ہے۔ یہاں localhost استعمال نہ کریں: Sonarr container کے اندر localhost، Sonarr ہے۔
Remote Path Mappings کو خالی چھوڑیں۔ یہ feature اس path کو تبدیل کرنے کے لیے ہے جس کی اطلاع download client دیتا ہے، تاکہ وہ ایسے path میں تبدیل ہو جائے جسے arr application دیکھ سکتی ہو۔ ایک مشترک /data mount کے ساتھ دونوں containers پہلے ہی ہر path پر متفق ہیں۔ یہی دوسری وجہ ہے کہ یہ layout ترتیب دینے کی محنت فائدہ مند ہے۔
Prowlarr کو Sonarr اور Radarr سے مربوط کریں
Prowlarr indexer کی تعریفیں دیگر ایپلی کیشنز میں منتقل کرتا ہے، اس لیے آپ indexer کو دو بار configure کرنے کے بجائے ایک بار configure کرتے ہیں۔ اس کے لیے ہر ایپلی کیشن سے ایک API (application programming interface) key درکار ہوتی ہے۔
Sonarr میں Settings کھولیں، پھر General کھول کر API key copy کریں۔ Prowlarr میں Settings کھولیں، پھر Apps کھولیں، Sonarr application شامل کریں، اور تین fields پُر کریں۔ Prowlarr Server http://prowlarr:9696 ہے۔ Sonarr Server http://sonarr:8989 ہے۔ API Key میں copy کی ہوئی value درج کریں۔ Test دبائیں۔ سبز نتیجہ ظاہر کرتا ہے کہ Prowlarr نے Compose network کے ذریعے Sonarr تک رسائی حاصل کر لی ہے۔ Radarr کے لیے http://radarr:7878 استعمال کرتے ہوئے یہی عمل دہرائیں۔
سرخ نتیجہ، جس میں connection refused دکھائی دے، تقریباً ہمیشہ غلط service name یا missing http:// prefix کی وجہ سے ہوتا ہے۔ تصدیق کریں کہ container کے اندر سے نام resolve ہو رہا ہے:
docker compose exec prowlarr curl -sS -o /dev/null -w '%{http_code}\n' http://sonarr:8989HTTP status code ثابت کرتا ہے کہ network path درست ہے۔ name resolution error ثابت کرتا ہے کہ service name غلط ہے۔
ثابت کریں کہ hardlinks واقعی بن رہے ہیں
link count دیکھے بغیر setup پر بھروسا نہ کریں۔ ایک item import ہونے کے بعد downloaded file اور library file کا موازنہ کریں:
stat -c '%i %h %n' /mnt/data/torrents/tv/*/*.mkv
stat -c '%i %h %n' /mnt/data/media/Shows/*/*/*.mkvپہلا عدد inode ہے اور دوسرا link count ہے۔ hardlinked file میں دونوں جگہ ایک ہی inode اور 2 کا link count دکھائی دیتا ہے۔ دو مختلف inodes، جن میں ہر ایک کا link count 1 ہو، اس بات کی نشاندہی کرتے ہیں کہ Sonarr نے file copy کی ہے۔ import log میں hardlink کے ناکام ہونے کا پیغام بھی ہوگا۔
disk کو بھی monitor کریں۔ import کے وقت df -h /mnt/data میں بمشکل تبدیلی آنی چاہیے، کیونکہ hardlink صرف ایک نام شامل کرتا ہے اور کوئی data نہیں بڑھاتا۔
اصل میں کیا خراب ہوتا ہے
Import کے دوران permission errors کا مطلب ہے کہ container کا user id library folder میں لکھ نہیں سکتا۔ پیغام Access to the path ... is denied ہے۔ ls -ln /mnt/data/media سے تصدیق کریں کہ owner id آپ کے PUID سے مطابقت رکھتا ہے، اور یاد رکھیں کہ container کے اندر داخل ہونے سے پہلے directories پر execute bit ضروری ہوتی ہے۔
root کی ملکیت والی نظر آنے والی files کا مطلب ہے کہ host directory کے موجود ہونے سے پہلے container شروع ہو گیا تھا، اس لیے Docker نے اسے root کے طور پر بنا دیا۔ Stack روکیں، directory کو chown کریں، اور اسے دوبارہ شروع کریں۔
qBittorrent سے torrent حذف کرنے کے بعد library file بھی غائب ملنے کا مطلب ہے کہ import ایک copy تھا جسے بعد میں حذف کر دیا گیا، یا آپ نے torrent entry کے بجائے data حذف کر دیا۔ حقیقی hardlink کے ساتھ ایک نام حذف کرنے سے دوسرا نام برقرار رہتا ہے، کیونکہ data اسی وقت آزاد ہوتا ہے جب link count صفر تک پہنچ جائے۔
اگر disk آپ کی شامل کردہ media سے کہیں زیادہ تیزی سے بھر رہی ہے تو یہ copy کے مسئلے کی سب سے مہنگی صورت ہے۔ مزید storage خریدنے سے پہلے اوپر دیا گیا stat check چلائیں۔
VPS سے اس stack کی ضروریات
تینوں arr ایپلیکیشنز ہلکی ہیں۔ یہ indexers کو poll کرتی ہیں، ایک چھوٹے SQLite database میں لکھتی ہیں، اور فائلوں کے نام تبدیل کرتی ہیں۔ 2 GB RAM والا server چاروں containers کو باآسانی چلا لیتا ہے۔ اصل load دوسرے کاموں سے آتا ہے۔ بڑا torrent download کرتے وقت download client disk input اور output کو saturate کر سکتا ہے، جبکہ اسی server پر video transcode کرنے والا media server CPU استعمال کرے گا۔ Media کو حقیقی throughput والے volume پر رکھیں، اور اگر server پر کوئی اور اہم کام بھی ہو رہا ہے تو download client پر bandwidth limit مقرر کریں۔ ان دوسرے کاموں کے لیے الگ سے وسائل مختص کریں؛ یہ فرض نہ کریں کہ اضافی گنجائش موجود ہے: self-hosted AFFiNE workspace مزید چار containers ہیں، جن کے پیچھے database بھی ہوتا ہے، اور 2 GB والے server پر یہ تقریباً پوری memory اپنے لیے چاہتا ہے۔ ہر اضافی service اتنے وسائل نہیں لیتی: self-hosted openGym workout tracker جیسی single-purpose service اسی server پر آسانی سے چل سکتی ہے، بشرطیکہ اسے اپنا TLS دیں اور اسے ایک سال کی training history دینے سے پہلے معلوم ہو کہ اس کی database file کہاں موجود ہے۔ جس چیز میں web application، Postgres database اور background worker queue شامل ہوں، وہ وسائل کے لحاظ سے AFFiNE کے قریب آتی ہے۔ اس لیے import کے دوران حد سامنے آنے سے پہلے فیصلہ کریں کہ self-hosted Chatwoot support desk اسی server پر ہونی چاہیے یا اپنے الگ server پر۔ Bursty workloads کے لیے مزید احتیاط ضروری ہے، کیونکہ import کے ساتھ ٹکراؤ average کے بجائے peak load کے وقت ہوتا ہے: اگر آپ self-hosted OneCLI جس میں ہر شخص کو اپنا sandboxed agent دیا جاتا ہے استعمال کرنے پر غور کر رہے ہیں، تو اس کی شائع شدہ sizing numbers کا موازنہ اس free capacity سے کریں جو qBittorrent کے پوری رفتار سے چلنے کے دوران حقیقتاً دستیاب ہو، نہ کہ اس capacity سے جو idle server پر free -h دکھاتا ہے۔
FAQ
Sonarr فائلوں کو hardlink کرنے کے بجائے copy کیوں کرتا ہے؟
اس کی وجہ یہ ہے کہ container کے نقطۂ نظر سے source اور destination مختلف filesystems پر ہیں۔ /downloads اور /tv جیسی دو الگ bind mounts، اس وقت بھی دو filesystems ہوتی ہیں جب دونوں ایک ہی host disk سے آتی ہوں۔ ہر container میں ایک ہی parent directory کو /data کے طور پر mount کریں، اور اسی کے اندر downloads اور library رکھیں۔ اس کے بعد link بنانا ممکن ہو جائے گا۔ دونوں فائلوں پر stat -c '%i %h %n' چلا کر نتیجہ جانچیں۔ دونوں میں ایک ہی inode اور 2 کی link count ہونی چاہیے۔
مجھے کون سا PUID اور PGID استعمال کرنا چاہیے؟
اس host account کی numeric id استعمال کریں جو media tree کا مالک ہے۔ یہ id آپ id -u اور id -g سے حاصل کر سکتے ہیں۔ نئے Ubuntu VPS پر عموماً دونوں کے لیے 1000 ہوتا ہے۔ stack کے ہر container میں یہی pair استعمال ہونا چاہیے، ورنہ ایک application ایسی فائلیں لکھے گی جنہیں دوسری modify نہیں کر سکے گی۔ values تبدیل کرنے کے بعد docker compose up -d --force-recreate سے containers دوبارہ بنائیں، اور موجودہ فائلوں کی ownership chown -R سے درست کریں۔
کیا مجھے ان web interfaces کو internet پر expose کرنا ضروری ہے؟
نہیں، اور آپ کو ایسا نہیں کرنا چاہیے۔ Compose file میں ہر published port کو 127.0.0.1 سے bind کریں۔ پھر interfaces تک SSH tunnel، VPN، یا ایسے reverse proxy کے ذریعے پہنچیں جو TLS (transport layer security) terminate کرے اور اپنی authentication شامل کرے۔ انہیں براہِ راست publish کرنا بظاہر سے بھی زیادہ خطرناک ہے، کیونکہ Docker اپنے firewall rules شامل کرتا ہے اور ufw کا deny rule اس traffic کو نہیں روک سکے گا۔
مجھے qBittorrent کا password کہاں ملے گا؟
LinuxServer.io image startup log میں admin user کے لیے temporary password دکھاتی ہے۔ اسے پڑھنے کے لیے docker compose logs qbittorrent | grep -i password چلائیں، پھر Options اور Web UI کے تحت permanent password مقرر کریں۔ اپنا password مقرر کرنے تک ہر restart پر نیا temporary password generate ہوتا رہے گا۔
کیا Jellyfin وہی folders استعمال کر سکتا ہے؟
ہاں، اور layout کا مقصد بھی یہی ہے۔ /mnt/data/media کو اپنے media server میں /media کے طور پر mount کریں۔ اس کی libraries /media/Movies اور /media/Shows میں ہوں گی، جبکہ Sonarr اور Radarr انہی directories میں /data/media کے ذریعے لکھیں گے۔ media server کو بھی وہی PUID اور PGID دیں، تاکہ وہ arr stack کی لکھی ہوئی فائلیں پڑھ سکے۔