SSD Nodes Learn 8GB RAM — $66/বছর
নির্দেশিকা Matt Connorদ্বারা Matt Connor · আপডেট করা হয়েছে 2026-08-01

এক Docker Compose ফাইলে Prowlarr, Sonarr ও Radarr

VPS-এ Prowlarr, Sonarr, Radarr ও qBittorrent একসঙ্গে চালান। সঠিক PUID, PGID ও volume layout ব্যবহার করে hardlink সচল রাখুন এবং permissions সমস্যা এড়ান।

আপনি যা তৈরি করছেন

একটি Docker Compose arr stack হলো 4টি container-এর সমন্বয়, যা একটি media library পরিচালনা করে: indexer সেটিংসের জন্য Prowlarr, series-এর জন্য Sonarr, films-এর জন্য Radarr এবং download client হিসেবে qBittorrent। এগুলো Compose network-এ service name ব্যবহার করে একে অপরের সঙ্গে যোগাযোগ করে এবং host-এর একটি folder tree শেয়ার করে। ইনস্টলেশনটি সংক্ষিপ্ত। Stackটি বছরের পর বছর নির্ভরযোগ্যভাবে কাজ করবে, নাকি প্রতি সপ্তাহে সমস্যা তৈরি করবে, তা volume layout-এর ওপর নির্ভর করে। তাই এই guide-এর বেশিরভাগ অংশ volume layout নিয়ে।

এই stack নিজে থেকে আপনার জন্য content খুঁজে বের করে না। Prowlarr-এ আপনি যে indexer যোগ করেন, সেগুলোই সেখানে থাকে। কোন indexer ব্যবহার করবেন, তা আপনার সিদ্ধান্ত এবং এর আইনি দায়িত্বও আপনার। এই guide-এ plumbing বিষয়গুলো, যেমন users, paths, permissions, container networking এবং কাজটি সঠিকভাবে চলছে কি না তা নিশ্চিত করার checks, আলোচনা করা হয়েছে।

আপনি যদি আগে কখনও Compose file না লিখে থাকেন, প্রথমে VPS-এর জন্য Docker Compose-এর মৌলিক বিষয়গুলো পড়ুন। এই post ধরে নিচ্ছে যে docker compose version ইতিমধ্যে আপনার server-এ কিছু output দেখায়।

হার্ডলিংক কেন ব্যর্থ হয়, এবং এটাই কেন মূল বিষয়

Sonarr কোনো ডাউনলোডের কাজ শেষ করলে সেটিকে আপনার লাইব্রেরিতে ইমপোর্ট করে। ডাউনলোড ফোল্ডার এবং লাইব্রেরি ফোল্ডার একই ফাইলসিস্টেমে থাকলে ইমপোর্টটি একটি হার্ডলিংক হয়: এটি ডিস্কে থাকা একই ডেটার দিকে নির্দেশ করা দ্বিতীয় নাম। এতে অতিরিক্ত কোনো জায়গা বা সময় লাগে না। টরেন্টটি পুরোনো নাম ব্যবহার করে সিড করতে থাকে, আর আপনার মিডিয়া সার্ভার নতুন নামটি পড়ে।

দুটি ফোল্ডার ভিন্ন ফাইলসিস্টেমে থাকলে kernel সেই লিংক তৈরি করতে পারে না। Sonarr তখন কপি করার পদ্ধতিতে ফিরে যায়। 40 GB-এর একটি সিজনে এখন 80 GB ডিস্ক স্পেস এবং input ও output-এর জন্য কয়েক মিনিট লাগে। ইমপোর্ট লগে রেকর্ড হয় যে হার্ডলিংক ব্যর্থ হয়েছে এবং ফাইলটি কপি করা হয়েছে। নির্দিষ্ট ডিস্ক সীমার VPS-এ এভাবেই এক সপ্তাহের মধ্যে স্পেস শেষ হয়ে যায়।

এখানেই সমস্যা তৈরি হয়। একটি container-এর ভিতরে bind mount একটি ফাইলসিস্টেম সীমানা। /mnt/data/torrents-কে /downloads হিসেবে এবং /mnt/data/media-কে /tv হিসেবে mount করলে, দুটিই একই host disk-এ থাকলেও Sonarr এগুলোকে দুটি আলাদা mount হিসেবে দেখে এবং তাদের মধ্যে লিংক তৈরি করতে অস্বীকার করে। LinuxServer.io-এর অফিসিয়াল image documentation-এ এটি সরাসরি বলা হয়েছে: আলাদা /downloads এবং /tv path ব্যবহার করলে হার্ডলিংক করার সুবিধা চলে যায়।

সমাধান হলো একটি mount ব্যবহার করা। মিডিয়ার সঙ্গে কাজ করা প্রতিটি container একই একক volume, /mnt/data:/data পাবে, এবং তারা যে প্রতিটি path ব্যবহার করবে তা এই volume-এর ভেতরের একটি folder হবে। একটি mount point, একটি ফাইলসিস্টেম, এবং কার্যকর হার্ডলিংক।

ব্যবহারকারী, গোষ্ঠী এবং ফোল্ডার তৈরি করুন

কনটেইনারগুলো PUID এবং PGID দ্বারা নির্ধারিত সংখ্যাসূচক user id ব্যবহার করে ফাইল লেখে। আপনার নিজের account ব্যবহার করুন, যাতে sudo ছাড়াই SSH-এর মাধ্যমে সেই ফাইল পড়তে ও সম্পাদনা করতে পারেন।

id -u
id -g

নতুন Ubuntu VPS-এ উভয় কমান্ড সাধারণত 1000 প্রিন্ট করে। এবার tree তৈরি করুন। এটি আপনার media থাকা disk-এ রাখুন এবং সম্পূর্ণ 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

উভয় লাইনে একই source device দেখাতে হবে। আলাদা দুটি device হলে container config-এ যা-ই সেট করুন না কেন, hardlink কখনও কাজ করবে না।

Library folder-গুলোর নাম ইচ্ছাকৃতভাবে Movies এবং Shows রাখা হয়েছে। আপনি যদি ইতিমধ্যে Jellyfin-কে আপনার media server হিসেবে চালান, তাহলে Jellyfin-এ /mnt/data/media-কে /media হিসেবে mount করুন। তাহলে এর library যথাক্রমে /media/Movies এবং /media/Shows-এ থাকবে, ঠিক যেভাবে ওই নির্দেশিকায় দেখানো হয়েছে।

পরিবেশ ফাইল

প্রতিটি সার্ভারে পরিবর্তিত মানগুলো Compose ফাইলের পাশে .env-এ রাখুন।

mkdir -p ~/arr && cd ~/arr

~/arr/.env লিখুন:

PUID=1000
PGID=1000
TZ=Etc/UTC
DATA_ROOT=/mnt/data

TZ-এ আপনার নিজস্ব time zone সেট করুন, যেমন Europe/Berlin। arr অ্যাপ্লিকেশনগুলো এই time zone অনুযায়ী কাজের সময়সূচি নির্ধারণ করে এবং log line-এ সময় লিখে। তাই ভুল মান দিলে পরে প্রতিটি 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

এই ফাইলে চারটি বিষয় কার্যকর ভূমিকা রাখে।

মিডিয়া নিয়ে কাজ করা তিনটি container-এ ${DATA_ROOT}:/data একই। Prowlarr এটি পায় না, কারণ Prowlarr কখনও কোনো মিডিয়া ফাইল খোলে না।

প্রতিটি web port 127.0.0.1-এর সঙ্গে bind করা হয়েছে। তাই Docker এটি শুধু loopback address-এ publish করে। সাধারণ 8989:8989 ব্যবহার করলে এটি প্রতিটি interface-এ publish হতো, এবং Docker-এর নিজস্ব firewall rule সেই traffic-কে ufw-এর deny rule এড়িয়ে সরাসরি পাঠাত। এই আচরণে মানুষ প্রায়ই অবাক হন। এর ব্যাখ্যা Docker কেন ufw-এর মধ্য দিয়ে সরাসরি port publish করে-এ রয়েছে।

Port 6881 ইচ্ছাকৃতভাবে সব interface-এ publish করা হয়েছে। এটি torrent listening port। Incoming peer connection-এর জন্য এটি reachable হতে হবে। sudo ufw allow 6881 দিয়ে এটি allow করুন। এই command নতুন হলে VPS-এর জন্য ufw firewall-এর প্রাথমিক বিষয় পড়ুন।

প্রতিটি application-এর config directory আলাদা। শুধু media volume-টি shared। প্রথমবার start করার আগে এগুলো তৈরি করুন, যাতে এগুলোর owner root-এর বদলে আপনার user হয়:

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

চারটি service-এরই running পড়া উচিত। July 2026 অনুযায়ী এই image-গুলো lscr.io-এ published, এবং latest tag বর্তমান stable release অনুসরণ করে। তাই upgrade যেন আকস্মিক না হয়ে আপনার সিদ্ধান্ত অনুযায়ী হয়, সে জন্য এর পরিবর্তে একটি version tag 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-এ পৌঁছাবে। স্থায়ী অ্যাক্সেসের জন্য, স্ট্যাকটিকে একাধিক অ্যাপের জন্য TLS certificate-সহ Traefik-এর পেছনে রাখুন, অথবা সার্ভারে নিজে পরিচালিত WireGuard VPN ব্যবহার করে প্রবেশ করুন। শুধুমাত্র নিজস্ব login page সামনে রেখে এই অ্যাপ্লিকেশনগুলোর কোনোটি public internet-এ চালানো উচিত নয়।

qBittorrent প্রথমবার চালু হলে একটি এলোমেলো administrator password তৈরি করে এবং তা container log-এ লিখে। password-টি পড়ুন, তারপর web interface-এ তা পরিবর্তন করুন:

docker compose logs qbittorrent | grep -i password

আপনি password পরিবর্তন না করলে প্রতিবার restart-এর সময় নতুন এলোমেলো password তৈরি হবে, এবং প্রতিবার আপনাকে আবার log পরীক্ষা করতে হবে।

প্রতিটি অ্যাপ্লিকেশনের ভেতরে পাথ নির্ধারণ করুন

qBittorrent-এ Options খুলুন, তারপর Downloads খুলে ডিফল্ট সেভ পাথ হিসেবে /data/torrents নির্ধারণ করুন। অসম্পূর্ণ ডাউনলোডের ফোল্ডারটিও একই ট্রির মধ্যে রাখুন, যেমন /data/torrents/incomplete/data-এর বাইরে যেকোনো স্থানে শেষ হওয়া ডাউনলোড লাইব্রেরিতে hardlink করা যাবে না।

Sonarr-এ Settings খুলুন, তারপর Media Management খুলে root folder হিসেবে /data/media/Shows যোগ করুন। Radarr-এ root folder হলো /data/media/Movies। এগুলো container-এর ভেতরের পাথ। Host path /mnt/data/media/Shows প্রত্যাখ্যাত হবে, কারণ container-এর দৃষ্টিকোণ থেকে ওই directory বিদ্যমান নয়।

Sonarr এবং Radarr উভয়টিতেই Settings খুলুন, তারপর Download Clients খুলে qBittorrent যোগ করুন। Host হলো qbittorrent এবং port হলো 8080। Service name hostname হিসেবে কাজ করে, কারণ Compose একটি internal DNS (domain name system) service ব্যবহার করে চারটি container-কে একই network-এ রাখে। এখানে localhost ব্যবহার করবেন না: Sonarr container-এর ভেতরে localhost হলো Sonarr।

Remote Path Mappings খালি রাখুন। এই বৈশিষ্ট্যটি download client যে path জানায়, সেটিকে arr application যে path দেখতে পারে সেই path-এ রূপান্তর করার জন্য ব্যবহৃত হয়। একটি shared /data mount থাকায় উভয় container ইতিমধ্যেই প্রতিটি path সম্পর্কে একমত থাকে। এই বিন্যাসটি ব্যবহার করার এটিই দ্বিতীয় কারণ।

Prowlarr-কে Sonarr এবং Radarr-এর সঙ্গে সংযুক্ত করুন

Prowlarr অন্যান্য অ্যাপ্লিকেশনে indexer-এর সংজ্ঞা পাঠায়। তাই একই indexer দুইবার কনফিগার করতে হয় না। এর জন্য প্রতিটি অ্যাপ্লিকেশন থেকে একটি API (application programming interface) key প্রয়োজন।

Sonarr-এ Settings খুলুন, তারপর General খুলে API key কপি করুন। Prowlarr-এ Settings খুলুন, তারপর Apps খুলে একটি Sonarr application যোগ করুন এবং তিনটি field পূরণ করুন। Prowlarr Server হলো http://prowlarr:9696। Sonarr Server হলো http://sonarr:8989। API Key হলো কপি করা মানটি। Test চাপুন। সবুজ ফলাফল দেখালে বুঝবেন Prowlarr Compose network-এর মাধ্যমে Sonarr-এ পৌঁছাতে পেরেছে। Radarr-এর জন্য http://radarr:7878 ব্যবহার করে একই কাজ পুনরাবৃত্তি করুন।

সংযোগ প্রত্যাখ্যাত হয়েছে—এমন লাল ফলাফল প্রায় সব সময় ভুল service name অথবা http:// prefix না থাকার কারণে দেখা যায়। Container-এর ভিতর থেকে name-টি resolve হচ্ছে কি না নিশ্চিত করুন:

docker compose exec prowlarr curl -sS -o /dev/null -w '%{http_code}\n' http://sonarr:8989

একটি HTTP status code প্রমাণ করে যে network path সঠিকভাবে কাজ করছে। Name resolution error প্রমাণ করে যে service name ভুল।

হার্ডলিংক সত্যিই তৈরি হচ্ছে কি না প্রমাণ করুন

লিংক কাউন্ট না দেখা পর্যন্ত সেটআপকে কার্যকর ধরে নেবেন না। একটি আইটেম ইমপোর্ট হওয়ার পর ডাউনলোড করা ফাইল এবং লাইব্রেরির ফাইল তুলনা করুন:

stat -c '%i %h %n' /mnt/data/torrents/tv/*/*.mkv
stat -c '%i %h %n' /mnt/data/media/Shows/*/*/*.mkv

প্রথম সংখ্যা হলো inode, আর দ্বিতীয় সংখ্যা হলো link count। হার্ডলিংক করা ফাইলে উভয় স্থানেই একই inode এবং 2-এর link count দেখা যায়। দুটি আলাদা inode এবং প্রতিটির link count 1 হলে Sonarr ফাইলটি কপি করেছে। সে ক্ষেত্রে import log-এ hardlink ব্যর্থ হয়েছে বলে উল্লেখ থাকবে।

ডিস্কের ব্যবহারও পর্যবেক্ষণ করুন। ইমপোর্টের সময় df -h /mnt/data-এ খুব সামান্য পরিবর্তন হওয়ার কথা, কারণ হার্ডলিংক কোনো ডেটা যোগ করে না, শুধু একটি নাম যোগ করে।

আসলে যা নষ্ট হয়

ইমপোর্টের সময় permission error দেখালে বোঝায়, container-এর user id library folder-এ লিখতে পারছে না। বার্তাটি হলো Access to the path ... is deniedls -ln /mnt/data/media দিয়ে পরীক্ষা করুন যে owner id আপনার PUID-এর সঙ্গে মেলে কি না। মনে রাখবেন, container কোনো directory-তে প্রবেশ করার আগে সেই directory-তে execute bit থাকতে হয়।

root-এর মালিকানাধীন বলে দেখা যাওয়া file-এর অর্থ হলো, host directory তৈরি হওয়ার আগে container চালু হয়েছিল। তাই Docker সেটি root হিসেবে তৈরি করেছে। stack বন্ধ করুন, directory-টি chown করুন, তারপর আবার চালু করুন।

qBittorrent থেকে কোনো torrent মুছে ফেলার পর library file-ও না থাকলে, ইমপোর্টটি এমন একটি copy ছিল যা পরে মুছে গেছে। অথবা আপনি torrent entry-এর বদলে data মুছে ফেলেছেন। প্রকৃত hardlink থাকলে একটি নাম মুছে ফেললেও অন্যটি অক্ষত থাকে। কারণ link count শূন্যে না পৌঁছানো পর্যন্ত data মুক্ত হয় না।

আপনার যোগ করা media-এর চেয়ে দ্রুত disk পূর্ণ হলে, সেটিই copy সমস্যার সবচেয়ে ব্যয়বহুল রূপ। আরও storage কেনার আগে উপরের stat check চালান।

এই স্ট্যাকের জন্য VPS থেকে যা প্রয়োজন

তিনটি arr অ্যাপ্লিকেশন কম রিসোর্স ব্যবহার করে। এগুলো indexer-এ poll করে, একটি ছোট SQLite database-এ লেখে এবং ফাইলের নাম পরিবর্তন করে। 2 GB RAM-এর একটি server চারটি container স্বচ্ছন্দে চালাতে পারে। মূল load অন্য দিক থেকে আসে। বড় torrent-এ download client disk input ও output সম্পূর্ণ ব্যবহার করতে পারে। একই server-এ media server ভিডিও transcode করলে CPU-তে চাপ পড়বে। যথেষ্ট বাস্তব throughput-যুক্ত একটি volume-এ media রাখুন। Server-এ অন্য কোনো গুরুত্বপূর্ণ কাজ চললে download client-এ bandwidth limit সেট করুন।

FAQ

কারণ container-এর দৃষ্টিতে source এবং destination ভিন্ন filesystem-এ রয়েছে। /downloads এবং /tv-এর মতো দুটি আলাদা bind mount ভিন্ন filesystem হিসেবে গণ্য হয়, এমনকি দুটিই একই host disk থেকে এলেও। প্রতিটি container-এ একটি অভিন্ন parent directory /data হিসেবে mount করুন এবং এর ভেতরে downloads ও library রাখুন। তাহলে link তৈরি করা সম্ভব হবে। উভয় ফাইলে stat -c '%i %h %n' চালিয়ে ফলাফল যাচাই করুন: একই inode এবং 2-এর link count থাকতে হবে।

কোন PUID এবং PGID ব্যবহার করব?

media tree-এর মালিক host account-এর numeric id ব্যবহার করুন। এই মান id -u এবং id -g থেকে পাওয়া যায়। নতুন Ubuntu VPS-এ সাধারণত উভয়ের মান 1000 হয়। Stack-এর প্রতিটি container-এ একই জোড়া ব্যবহার করতে হবে। অন্যথায় একটি application এমন ফাইল লিখতে পারে, যা অন্য application পরিবর্তন করতে পারবে না। মান পরিবর্তনের পর docker compose up -d --force-recreate দিয়ে container-গুলো পুনরায় তৈরি করুন এবং chown -R দিয়ে বিদ্যমান ফাইলগুলোর ownership ঠিক করুন।

এই web interface-গুলো কি internet-এ প্রকাশ করতে হবে?

না, এবং তা করা উচিত নয়। Compose file-এ প্রতিটি published port 127.0.0.1-এর সঙ্গে bind করুন। এরপর SSH tunnel, VPN, অথবা TLS (transport layer security) শেষ করে নিজস্ব authentication যুক্ত করে এমন reverse proxy-এর মাধ্যমে interface-গুলোতে প্রবেশ করুন। সরাসরি প্রকাশ করা ধারণার চেয়েও বেশি ঝুঁকিপূর্ণ। কারণ Docker নিজস্ব firewall rule যুক্ত করে, এবং একটি ufw deny rule সেই traffic আটকাতে পারবে না।

qBittorrent-এর password কোথায় পাব?

LinuxServer.io image তার startup log-এ admin user-এর জন্য একটি অস্থায়ী password দেখায়। এটি পড়তে docker compose logs qbittorrent | grep -i password চালান। এরপর Options এবং Web UI-এর অধীনে স্থায়ী password সেট করুন। নিজের password সেট না করা পর্যন্ত প্রতিবার restart-এর সময় একটি নতুন অস্থায়ী password তৈরি হয়।

Jellyfin কি একই folder ব্যবহার করতে পারে?

হ্যাঁ, এবং layout-এর উদ্দেশ্যই এটি। /mnt/data/media-কে media server-এ /media হিসেবে mount করুন। তাহলে এর library-গুলো /media/Movies এবং /media/Shows-এ থাকবে, আর Sonarr ও Radarr /data/media-এর মাধ্যমে একই directory-তে লিখবে। media server-কে একই PUID এবং PGID দিন, যাতে arr stack যে ফাইলগুলো লেখে সেগুলো পড়তে পারে।

#sonarr#radarr#prowlarr#docker-compose#self-hosting