Docker Compose में Prowlarr, Sonarr, Radarr कैसे सेटअप
एक ही Docker Compose फाइल का उपयोग करके Prowlarr, Sonarr, Radarr और qBittorrent को VPS पर सेटअप करें। सही PUID, PGID और volume layout के साथ hardlinks की समस्या को हल करें।
आप क्या बना रहे हैं
Docker Compose arr stack चार containers का एक समूह है जो एक media library को manage करता है: indexer settings के लिए Prowlarr, series के लिए Sonarr, films के लिए Radarr, और download client के रूप में qBittorrent। ये सभी Compose network पर service name के माध्यम से एक-दूसरे से बात करते हैं और host पर एक ही folder tree साझा करते हैं। इसका install छोटा है। वह हिस्सा जो यह तय करता है कि यह stack वर्षों तक ठीक चलेगा या हर हफ्ते समस्या देगा, वह volume layout है, इसलिए इस guide का अधिकांश भाग इसी के बारे में है।
यह stack आपके लिए content नहीं ढूँढता है। Prowlarr में आप जो भी indexers जोड़ते हैं, वे इसमें रहते हैं, और आप कौन से indexers का उपयोग करते हैं, यह आपका निर्णय और आपकी कानूनी जिम्मेदारी है। यह guide plumbing को कवर करती है: users, paths, permissions, container networking, और वे जाँच जो यह सिद्ध करती हैं कि यह काम कर रहा है।
यदि आपने कभी Compose file नहीं लिखी है, तो पहले the Docker Compose basics for a VPS पढ़ें। यह post मानती है कि docker compose version आपके server पर पहले से ही कुछ output देता है।
Hardlinks क्यों विफल होते हैं, और यह पूरी प्रक्रिया क्यों महत्वपूर्ण है
जब Sonarr डाउनलोड पूरा करता है, तो वह फाइल को आपकी लाइब्रेरी में import करता है। यदि डाउनलोड फोल्डर और लाइब्रेरी फोल्डर एक ही filesystem पर स्थित हैं, तो import एक hardlink होता है: डिस्क पर मौजूद समान डेटा को इंगित करने वाला एक दूसरा नाम। इसमें कोई अतिरिक्त स्थान या समय नहीं लगता है। Torrent पुराने नाम से seeding जारी रखता है जबकि आपका मीडिया सर्वर नए नाम से फाइल को पढ़ता है।
यदि दोनों फोल्डर अलग-अलग filesystems पर हैं, तो kernel वह link नहीं बना सकता। Sonarr वापस copy करने की प्रक्रिया पर आ जाता है। एक 40 GB का सीजन अब डिस्क पर 80 GB स्थान लेता है और इसमें इनपुट-आउटपुट में कई मिनट लगते हैं, और import log में दर्ज होता है कि hardlink विफल रहा और इसके बजाय फाइल को copy किया गया। फिक्स्ड डिस्क क्षमता वाले VPS पर, इसी कारण से लोगों का स्टोरेज एक सप्ताह में ही समाप्त हो जाता है।
यहाँ मुख्य समस्या है। Container के भीतर, एक bind mount एक filesystem सीमा होती है। यदि आप /mnt/data/torrents को /downloads के रूप में और /mnt/data/media को /tv के रूप में mount करते हैं, तो भले ही दोनों एक ही होस्ट डिस्क पर हों, Sonarr उन्हें दो अलग-अलग mounts के रूप में देखता है और उनके बीच link बनाने से मना कर देता है। आधिकारिक LinuxServer.io इमेज डॉक्यूमेंटेशन में यह स्पष्ट रूप से कहा गया है: अलग-अलग /downloads और /tv पाथ का उपयोग करने से hardlink करने की क्षमता समाप्त हो जाती है।
इसका समाधान एक ही mount है। मीडिया का उपयोग करने वाले प्रत्येक container को एक ही volume, /mnt/data:/data, दिया जाना चाहिए, और उनके द्वारा उपयोग किया जाने वाला प्रत्येक पाथ उसी के भीतर एक फोल्डर होना चाहिए। एक mount point, एक filesystem, और सही ढंग से काम करते hardlinks।
उपयोगकर्ता, समूह और फोल्डर बनाएँ
Containers फाइलों को एक numeric user id के रूप में लिखते हैं, जिसे PUID और PGID द्वारा सेट किया जाता है। अपने स्वयं के account का उपयोग करें ताकि आप SSH के माध्यम से sudo के बिना उन फाइलों को पढ़ और संपादित कर सकें।
id -u
id -gदोनों आमतौर पर एक fresh 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आगे बढ़ने से पहले जाँच लें कि यह वास्तव में एक ही filesystem है:
df --output=source,target /mnt/data/torrents /mnt/data/mediaदोनों लाइनों को एक ही source device दिखाना चाहिए। दो अलग-अलग devices का मतलब है कि hardlinks कभी काम नहीं करेंगे, चाहे आप container config में कुछ भी सेट करें।
Library फोल्डर का नाम जानबूझकर Movies और Shows रखा गया है। यदि आप पहले से ही Jellyfin को अपने मीडिया सर्वर के रूप में चला रहे हैं, तो /mnt/data/media को Jellyfin में /media के रूप में mount करें और इसकी libraries /media/Movies और /media/Shows पर आ जाएँगी, बिल्कुल वहीं जहाँ वह गाइड उन्हें रखती है।
Environment file
सर्वर के अनुसार बदलने वाले मानों को .env में रखें, जो Compose file के बगल में स्थित हो।
mkdir -p ~/arr && cd ~/arr~/arr/.env लिखें:
PUID=1000
PGID=1000
TZ=Etc/UTC
DATA_ROOT=/mnt/dataTZ को अपने zone पर सेट करें, जैसे कि Europe/Berlin। arr applications इसी zone में कार्यों को schedule करते हैं और log lines को stamp करते हैं, इसलिए गलत मान होने पर बाद में हर 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 फाइल नहीं खोलता है।
प्रत्येक 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 के लिए इसका reachable होना आवश्यक है। इसे sudo ufw allow 6881 के साथ allow करें, और यदि यह command आपके लिए नई है तो VPS के लिए ufw firewall की बुनियादी जानकारी पढ़ें।
Config 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 पढ़ना चाहिए। जुलाई 2026 तक ये images lscr.io पर प्रकाशित की जाती हैं और latest tag वर्तमान stable release का अनुसरण करता है, इसलिए यदि आप चाहते हैं कि upgrades एक निर्णय हो न कि कोई आश्चर्य, तो इसके बजाय एक version tag को pin करें।
वेब इंटरफेस तक सुरक्षित रूप से पहुँचें
चूंकि ports loopback पर हैं, इसलिए अभी कुछ भी expose नहीं हुआ है। उन्हें अपनी मशीन से 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अब आपके ब्राउज़र में http://127.0.0.1:8989 सर्वर पर मौजूद Sonarr तक पहुँच प्रदान करता है। स्थायी पहुँच के लिए, stack को कई apps के लिए TLS certificates के साथ Traefik के पीछे रखें, या स्वयं द्वारा host किए गए WireGuard VPN के माध्यम से सर्वर तक पहुँचें। इनमें से किसी भी application को केवल उनके अपने login page के साथ public internet पर नहीं होना चाहिए। यदि आप reverse proxy का मार्ग चुनते हैं और चार अलग-अलग application logins को याद रखने के बजाय सभी चार इंटरफेस के लिए एक ही account चाहते हैं, तो Authentik आपको self-hosted single sign-on की सुविधा देता है जिसे Traefik forward auth के साथ हर request पर लागू कर सकता है।
qBittorrent पहली बार start होने पर एक random administrator password generate करता है और उसे container log में print करता है। उसे पढ़ें, फिर web interface में उसे बदलें:
docker compose logs qbittorrent | grep -i passwordयदि आप इसे बदलना छोड़ देते हैं, तो हर restart पर एक नया random password generate होगा, और आपको हर बार logs में वापस जाना पड़ेगा।
Set the paths inside each application
In qBittorrent, open Options, then Downloads, and set the default save path to /data/torrents. Keep the incomplete-downloads folder inside the same tree, such as /data/torrents/incomplete. A download that finishes anywhere outside /data cannot be hardlinked into the library.
In Sonarr, open Settings, then Media Management, and add the root folder /data/media/Shows. In Radarr the root folder is /data/media/Movies. These are paths inside the container. The host path /mnt/data/media/Shows is rejected, because that directory does not exist from the container's point of view.
In both Sonarr and Radarr, open Settings, then Download Clients, and add qBittorrent. The host is qbittorrent and the port is 8080. The service name works as a hostname because Compose puts all four containers on one network with an internal DNS (domain name system) service. Do not use localhost here: inside the Sonarr container, localhost is Sonarr.
Leave Remote Path Mappings empty. That feature exists to translate a path the download client reports into a path the arr application can see. With one shared /data mount, both containers already agree on every path, which is the second reason this layout is worth the effort.
Prowlarr को Sonarr और Radarr से कनेक्ट करना
Prowlarr इंडेक्सर डेफिनिशन को अन्य ऐप्लिकेशन्स में पुश करता है, इसलिए आपको एक इंडेक्सर को दो बार के बजाय केवल एक बार कॉन्फ़िगर करना पड़ता है। इसके लिए प्रत्येक से एक API (application programming interface) की (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 पर यही प्रक्रिया दोहराएं।
कनेक्शन रिफ्यूज होने का लाल परिणाम लगभग हमेशा गलत सर्विस नाम या गायब http:// प्रीफ़िक्स का संकेत देता है। पुष्टि करें कि नाम कंटेनर के अंदर से रिज़ॉल्व हो रहा है:
docker compose exec prowlarr curl -sS -o /dev/null -w '%{http_code}\n' http://sonarr:8989एक HTTP स्टेटस कोड यह साबित करता है कि नेटवर्क पाथ सही है। नाम रिज़ॉल्यूशन एरर यह साबित करता है कि सर्विस का नाम गलत है।
यह सिद्ध करें कि hardlinks वास्तव में काम कर रहे हैं
जब तक आप link count न देख लें, तब तक setup पर भरोसा न करें। एक item import हो जाने के बाद, download की गई 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 है। जिस file को hardlink किया गया है, वह दोनों स्थानों पर समान inode दिखाएगी और उसका link count 2 होगा। यदि दो अलग-अलग inode हैं और प्रत्येक का link count 1 है, तो इसका अर्थ है कि Sonarr ने file को copy किया है, और import log में यह लिखा होगा कि hardlink विफल रहा।
Disk पर भी नज़र रखें। 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 के स्वामित्व में दिखाई देती हैं, तो इसका मतलब है कि 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 की आवश्यकताएं
तीनों arr applications हल्के हैं। वे indexers को poll करते हैं, एक छोटी SQLite database में लिखते हैं और files का नाम बदलते हैं। 2 GB RAM वाला server चारों containers को आसानी से चला सकता है। वास्तविक load दूसरी जगह से आता है। बड़े torrents पर download client disk input और output को पूरी तरह व्यस्त कर सकता है, और उसी box पर video transcode करने वाला media server CPU का उपयोग करेगा। Media को वास्तविक throughput वाले volume पर रखें। यदि server पर कोई अन्य महत्वपूर्ण workload भी चल रहा है, तो download client पर bandwidth limit लगाएँ। उन workloads के लिए अलग से संसाधन निर्धारित करें। यह न मानें कि पर्याप्त headroom उपलब्ध है: self-hosted AFFiNE workspace अपने पीछे database के साथ चार और containers चलाता है, और 2 GB वाले box पर वह अधिकांश memory अकेले चाहता है। हर अतिरिक्त service का खर्च इतना अधिक नहीं होता। self-hosted openGym workout tracker जैसी single-purpose service box पर आसानी से साझा की जा सकती है, बशर्ते आप उसे अपना TLS दें और एक वर्ष का training history सौंपने से पहले यह जान लें कि उसकी database file कहाँ है। जिस setup में web application, Postgres database और background worker queue शामिल हों, वह इस range में AFFiNE के अधिक करीब होता है। इसलिए import के बीच में limit का पता चलने से पहले तय करें कि self-hosted Chatwoot support desk इसी server पर होना चाहिए या अपने अलग server पर। Bursty workloads में और अधिक सावधानी आवश्यक है, क्योंकि import के साथ टकराव average नहीं बल्कि peak load का होता है। यदि आप self-hosted OneCLI, जो प्रत्येक व्यक्ति को अपना sandboxed agent देता है चलाने पर विचार कर रहे हैं, तो उसके प्रकाशित sizing numbers की तुलना उस वास्तविक free capacity से करें जो qBittorrent के पूरी तरह load पर चलने के दौरान उपलब्ध रहती है। तुलना idle box पर free -h द्वारा दिखाए गए आंकड़े से न करें।
FAQ
Sonarr फाइलों को hardlink करने के बजाय copy क्यों करता है?
क्योंकि container के नजरिए से source और destination अलग-अलग filesystems पर हैं। दो अलग bind mounts, जैसे कि /downloads और /tv, दो अलग 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 -u और id -g से प्राप्त कर सकते हैं। एक नए Ubuntu VPS पर यह आमतौर पर दोनों के लिए 1000 होता है। stack में प्रत्येक container को एक ही pair का उपयोग करना चाहिए, अन्यथा एक application ऐसी फाइलें लिख देगा जिन्हें दूसरा modify नहीं कर पाएगा। मान बदलने के बाद, docker compose up -d --force-recreate के साथ containers को recreate करें और chown -R के साथ मौजूदा फाइलों को ठीक करें।
क्या मुझे इन web interfaces को internet पर expose करने की आवश्यकता है?
नहीं, और आपको ऐसा नहीं करना चाहिए। Compose file में प्रत्येक published port को 127.0.0.1 पर bind करें, फिर SSH tunnel, VPN, या TLS (transport layer security) terminate करने वाले और अपना authentication जोड़ने वाले reverse proxy के माध्यम से interfaces तक पहुँचें। उन्हें सीधे publish करना दिखने से कहीं अधिक बुरा है, क्योंकि Docker अपने स्वयं के firewall rules डाल देता है और एक ufw deny rule उस traffic को नहीं रोकेगा।
मुझे qBittorrent का password कहाँ मिलेगा?
LinuxServer.io image अपने startup log में admin user के लिए एक temporary password print करती है। इसे पढ़ने के लिए 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 लिखता है।