Docker Compose arr stack sa isang VPS
Patakbuhin ang Prowlarr, Sonarr, Radarr at qBittorrent sa iisang Docker Compose file, gamit ang tamang PUID, PGID at volume layout para gumana ang hardlinks.
Ano ang binubuo mo
Ang Docker Compose arr stack ay binubuo ng apat na container na namamahala sa media library: Prowlarr para sa mga setting ng indexer, Sonarr para sa mga series, Radarr para sa mga pelikula, at qBittorrent bilang download client. Nakikipag-ugnayan ang mga ito sa isa’t isa sa Compose network gamit ang service name, at gumagamit ang mga ito ng iisang folder tree sa host. Maikli ang proseso ng pag-install. Ang volume layout ang pangunahing nakaaapekto kung gagana nang maayos ang stack sa loob ng maraming taon o kung magdudulot ito ng mga problema bawat linggo, kaya nakatuon dito ang malaking bahagi ng gabay na ito.
Hindi awtomatikong naghahanap ng content ang stack. Pinamamahalaan ng Prowlarr ang mga indexer na idinaragdag mo rito, at ikaw ang nagpapasya kung aling mga indexer ang gagamitin at ikaw rin ang may legal na pananagutan. Saklaw ng gabay na ito ang plumbing: mga user, path, permission, container networking, at mga pagsusuring nagpapatunay na gumagana ito.
Kung hindi ka pa nakakasulat ng Compose file, basahin muna ang mga pangunahing kaalaman sa Docker Compose para sa VPS. Ipinapalagay ng post na ito na nagpapakita na ang docker compose version ng output sa iyong server.
Bakit hindi gumagana ang hardlink, at bakit ito ang pangunahing usapin
Kapag tapos na ang Sonarr sa pag-download, ini-import nito ang file sa iyong library. Kung nasa parehong filesystem ang download folder at library folder, hardlink ang import: pangalawang pangalan ito na tumutukoy sa parehong data sa disk. Wala itong dagdag na space at hindi ito nangangailangan ng oras sa pagkopya. Patuloy na nagse-seed ang torrent gamit ang dating pangalan habang binabasa ng media server ang bagong pangalan.
Kung nasa magkaibang filesystem ang dalawang folder, hindi magagawa ng kernel ang link na iyon. Bumabalik ang Sonarr sa pagkopya. Ang 40 GB na season ay mangangailangan ngayon ng 80 GB na disk space at ilang minuto ng input at output, at itinatala ng import log na nabigo ang hardlink at sa halip ay kinopya ang file. Sa VPS na may fixed na disk allowance, ganito nauubusan ng space ang mga tao sa loob ng isang linggo.
Narito ang karaniwang problema. Sa loob ng container, ang bind mount ay filesystem boundary. I-mount ang /mnt/data/torrents bilang /downloads at ang /mnt/data/media bilang /tv, at kahit nasa iisang host disk ang mga ito, nakikita ng Sonarr ang dalawang magkahiwalay na mount at tumatangging gumawa ng link sa pagitan ng mga ito. Direktang sinasabi ito sa opisyal na documentation ng LinuxServer.io image: kapag hiwalay na /downloads at /tv paths ang ginamit, mawawala ang kakayahang gumamit ng hardlink.
Ang solusyon ay isang mount. Bawat container na gumagamit ng media ay dapat gumamit ng parehong iisang volume, /mnt/data:/data, at bawat path na ginagamit nito ay dapat na folder sa loob nito. Isang mount point, isang filesystem, at gumaganang hardlink.
Gumawa ng user, group, at mga folder
Nagsusulat ang mga container ng mga file gamit ang numeric user ID na itinatakda ng PUID at PGID. Gamitin ang sarili mong account para mabasa at ma-edit mo ang mga file na iyon sa SSH nang hindi gumagamit ng sudo.
id -u
id -gKaraniwang parehong nagpi-print ang mga ito ng 1000 sa bagong Ubuntu VPS. Ngayon, gawin ang tree. Ilagay ito sa disk na naglalaman ng iyong media, at panatilihin ang buong tree sa iisang disk na iyon.
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/dataTiyaking iisang filesystem talaga ito bago magpatuloy:
df --output=source,target /mnt/data/torrents /mnt/data/mediaDapat magpakita ang dalawang linya ng parehong source device. Kung magkaiba ang device, hindi kailanman gagana ang hardlinks, anuman ang itakda mo sa container config.
Sadyang pinangalanang Movies at Shows ang mga folder ng library. Kung nagpapatakbo ka na ng Jellyfin bilang iyong media server, i-mount ang /mnt/data/media sa Jellyfin bilang /media, at mapupunta ang mga library nito sa /media/Movies at /media/Shows, eksaktong kung saan inilalagay ng gabay na iyon ang mga ito.
Ang environment file
Panatilihin ang mga value na nagbabago sa bawat server sa .env, katabi ng Compose file.
mkdir -p ~/arr && cd ~/arrIsulat ang ~/arr/.env:
PUID=1000
PGID=1000
TZ=Etc/UTC
DATA_ROOT=/mnt/dataItakda ang TZ sa sarili mong zone, gaya ng Europe/Berlin. Ginagamit ng arr applications ang zone na iyon sa pag-iskedyul ng mga task at paglalagay ng timestamp sa mga log line, kaya magiging nakalilito ang lahat ng log kapag mali ang value.
Ang Compose file
Isulat ang ~/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-stoppedMay apat na bagay sa file na ito ang gumaganap ng mahalagang papel.
Magkapareho ang ${DATA_ROOT}:/data sa tatlong container na gumagamit ng media. Hindi ito kailangan ng Prowlarr dahil hindi kailanman nagbubukas ang Prowlarr ng media file.
Naka-bind ang bawat web port sa 127.0.0.1, kaya sa loopback address lamang ito ipinapublish ng Docker. Kapag plain na 8989:8989 ang ginamit, mai-publish ito sa bawat interface, at direktang daraan ang traffic sa sariling firewall rules ng Docker sa isang ufw deny rule. Palaging nagugulat ang mga tao sa behavior na ito. Ipinaliwanag ito sa kung bakit direktang ipinapasa ng Docker ang mga port sa ufw.
Sadyang ipinapublish ang port 6881 sa lahat ng interface. Ito ang torrent listening port, at dapat itong maabot para sa mga incoming peer connection. Payagan ito gamit ang sudo ufw allow 6881. Basahin din ang mga pangunahing kaalaman sa ufw firewall para sa VPS kung bago sa iyo ang command na iyon.
Magkahiwalay ang mga config directory ng bawat application, at ang media volume lamang ang shared. Gawin ang mga ito bago ang unang start upang pagmamay-ari ang mga ito ng iyong user sa halip na ng root:
mkdir -p ~/arr/config/prowlarr ~/arr/config/sonarr ~/arr/config/radarr ~/arr/config/qbittorrent
docker compose up -d
docker compose psDapat basahin ng lahat ng apat na service ang running. Noong July 2026, ang mga image na ito ay naka-publish sa lscr.io, at sinusundan ng latest tag ang kasalukuyang stable release. Gumamit na lang ng version tag kung gusto mong planado ang mga upgrade sa halip na maging biglaang sorpresa.
Ligtas na pag-access sa mga web interface
Dahil nasa loopback ang mga port, wala pang nakalantad sa network. I-forward ang mga ito sa SSH mula sa sarili mong machine:
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-serverNgayon, maaabot ng http://127.0.0.1:8989 sa iyong browser ang Sonarr sa server. Para sa permanenteng access, ilagay ang stack sa likod ng Traefik na may TLS certificate para sa ilang app, o i-access ang server gamit ang WireGuard VPN na ikaw mismo ang nagho-host. Hindi dapat direktang nasa public internet ang alinman sa mga application na ito na sarili lamang nilang login page ang proteksiyon.
Gumagawa ang qBittorrent ng random na administrator password sa unang pag-start at ipinapakita ito sa container log. Basahin ito, pagkatapos ay palitan sa web interface:
docker compose logs qbittorrent | grep -i passwordKung lalaktawan mo ang pagpapalit, bagong random na password ang bubuo sa bawat restart, at kailangan mong bumalik sa logs sa bawat pagkakataon.
Itakda ang mga path sa loob ng bawat application
Sa qBittorrent, buksan ang Options, pagkatapos ay Downloads, at itakda ang default save path sa /data/torrents. Panatilihin ang folder para sa mga hindi pa kumpletong download sa loob ng parehong tree, gaya ng /data/torrents/incomplete. Ang download na natapos sa labas ng /data ay hindi maaaring lagyan ng hardlink papunta sa library.
Sa Sonarr, buksan ang Settings, pagkatapos ay Media Management, at idagdag ang root folder na /data/media/Shows. Sa Radarr, ang root folder ay /data/media/Movies. Mga path ito sa loob ng container. Tinatanggihan ang host path na /mnt/data/media/Shows dahil hindi umiiral ang directory na iyon mula sa pananaw ng container.
Sa Sonarr at Radarr, buksan ang Settings, pagkatapos ay Download Clients, at idagdag ang qBittorrent. Ang host ay qbittorrent at ang port ay 8080. Gumagana ang service name bilang hostname dahil inilalagay ng Compose ang lahat ng apat na container sa iisang network na may internal DNS (domain name system) service. Huwag gamitin ang localhost dito: sa loob ng Sonarr container, ang localhost ay Sonarr.
Iwanang walang laman ang Remote Path Mappings. Ginagamit ang feature na ito upang isalin ang path na iniuulat ng download client tungo sa path na nakikita ng arr application. Dahil iisang shared /data mount ang ginagamit, pareho nang tumutugma ang mga path sa dalawang container. Ito ang ikalawang dahilan kung bakit sulit ang layout na ito.
I-connect ang Prowlarr sa Sonarr at Radarr
Itinutulak ng Prowlarr ang mga definition ng indexer sa iba pang application, kaya isang beses mo lang iko-configure ang isang indexer. Kailangan nito ng API (application programming interface) key mula sa bawat isa.
Sa Sonarr, buksan ang Settings, pagkatapos ay General, at kopyahin ang API key. Sa Prowlarr, buksan ang Settings, pagkatapos ay Apps, magdagdag ng Sonarr application, at punan ang tatlong field. Ang Prowlarr Server ay http://prowlarr:9696. Ang Sonarr Server ay http://sonarr:8989. Ang API Key ay ang kinopyang value. Pindutin ang Test. Ang berdeng resulta ay nangangahulugang naabot ng Prowlarr ang Sonarr sa Compose network. Ulitin ito gamit ang Radarr sa http://radarr:7878.
Ang pulang resultang nagsasabing tinanggihan ang connection ay halos palaging nangangahulugang mali ang service name o nawawala ang http:// prefix. Tiyaking nareresolba ang pangalan mula sa loob ng container:
docker compose exec prowlarr curl -sS -o /dev/null -w '%{http_code}\n' http://sonarr:8989Pinatutunayan ng HTTP status code na maayos ang network path. Pinatutunayan ng name resolution error na mali ang service name.
Patunayan na aktuwal na ginagawa ang hardlink
Huwag pagkatiwalaan ang setup hangga't hindi mo nakikita ang link count. Pagkatapos ma-import ang isang item, ikumpara ang na-download na file sa file sa library:
stat -c '%i %h %n' /mnt/data/torrents/tv/*/*.mkv
stat -c '%i %h %n' /mnt/data/media/Shows/*/*/*.mkvAng unang numero ay ang inode at ang ikalawa ay ang link count. Ang file na ginamitan ng hardlink ay may parehong inode sa dalawang lokasyon at link count na 2. Ang magkaibang inode, na parehong may link count na 1, ay nangangahulugang kinopya ng Sonarr ang file, at sasabihin sa import log na nabigo ang hardlink.
Bantayan din ang disk. Halos hindi dapat magbago ang df -h /mnt/data kapag may import, dahil nagdaragdag lamang ng pangalan ang hardlink at walang data.
Ano talaga ang nasisira
Ang mga error sa permission sa pag-import ay nangangahulugang hindi makapagsulat ang user id ng container sa library folder. Ang mensahe ay Access to the path ... is denied. Gamitin ang ls -ln /mnt/data/media upang tingnan kung tumutugma ang owner id sa iyong PUID, at tandaan na kailangan ng mga directory ng execute bit bago mapasok ng container ang mga ito.
Ang mga file na lumilitaw na pagmamay-ari ng root ay nangangahulugang nagsimula ang container bago umiiral ang host directory, kaya ginawa ito ng Docker bilang root. Ihinto ang stack, chown ang directory, at simulan itong muli.
Kapag nag-delete ka ng torrent mula sa qBittorrent at nawala ang library file, nangangahulugan itong copy ang ginawa sa pag-import na kalaunan ay naalis, o dinelete mo ang data sa halip na ang torrent entry. Sa totoong hardlink, nananatili ang isa pang pangalan kapag inalis ang isang pangalan, dahil inilalabas lamang ang data kapag umabot sa zero ang link count.
Ang disk na mas mabilis mapuno kaysa sa dami ng idinagdag mong media ay ang pinakamahal na anyo ng copy problem. Patakbuhin muna ang stat check sa itaas bago bumili ng karagdagang storage.
Mga kailangan ng stack na ito mula sa VPS
Magaan ang tatlong arr application. Nagpo-poll ang mga ito sa mga indexer, nagsusulat sa maliit na SQLite database, at nagpapalit ng pangalan ng mga file. Kayang patakbuhin ng server na may 2 GB RAM ang lahat ng apat na container nang maayos. Sa ibang bahagi nanggagaling ang load. Maaaring sagarin ng download client ang disk input at output kapag malalaki ang torrent, at gagamit ng CPU ang media server na nagta-transcode ng video sa parehong box. Ilagay ang media sa volume na may sapat na aktuwal na throughput, at magtakda ng bandwidth limit sa download client kung may iba pang mahalagang gawain ang server.
FAQ
Bakit kumokopya ng files ang Sonarr sa halip na gumawa ng hardlink?
Dahil magkaibang filesystem ang source at destination ayon sa pananaw ng container. Ang dalawang magkahiwalay na bind mount, gaya ng /downloads at /tv, ay itinuturing na dalawang filesystem kahit mula sa iisang host disk ang mga ito. I-mount ang iisang parent directory bilang /data sa bawat container, ilagay dito ang downloads at library, at magiging posible ang paglikha ng link. Kumpirmahin ang resulta gamit ang stat -c '%i %h %n' sa parehong files: dapat pareho ang inode at may link count na 2.
Anong PUID at PGID ang dapat kong gamitin?
Gamitin ang numeric ID ng host account na nagmamay-ari sa media tree. Makukuha mo ito gamit ang id -u at id -g. Sa bagong Ubuntu VPS, karaniwan itong 1000 para sa dalawa. Dapat gamitin ng bawat container sa stack ang parehong pair. Kung hindi, maaaring magsulat ang isang application ng files na hindi mabago ng ibang application. Pagkatapos baguhin ang mga value, i-recreate ang mga container gamit ang docker compose up -d --force-recreate at ayusin ang mga existing file gamit ang chown -R.
Kailangan ko bang i-expose sa internet ang mga web interface na ito?
Hindi, at hindi ito dapat gawin. I-bind ang bawat published port sa 127.0.0.1 sa Compose file. Pagkatapos, i-access ang mga interface gamit ang SSH tunnel, VPN, o reverse proxy na nagte-terminate ng TLS (transport layer security) at nagdaragdag ng sarili nitong authentication. Mas mapanganib ang direktang pag-publish kaysa sa inaakala, dahil naglalagay ang Docker ng sarili nitong firewall rules at hindi mapipigilan ng ufw deny rule ang traffic na iyon.
Saan ko makikita ang password ng qBittorrent?
Nagpi-print ang LinuxServer.io image ng temporary password para sa user na admin sa startup log nito. Patakbuhin ang docker compose logs qbittorrent | grep -i password para basahin ito, pagkatapos ay magtakda ng permanent password sa ilalim ng Options at Web UI. Gumagawa ng bagong temporary password sa bawat restart hanggang magtakda ka ng sarili mong password.
Magagamit ba ng Jellyfin ang parehong folders?
Oo, at iyon ang layunin ng layout. I-mount ang /mnt/data/media sa media server bilang /media. Matatagpuan ang mga library nito sa /media/Movies at /media/Shows, habang nagsusulat ang Sonarr at Radarr sa parehong directories sa pamamagitan ng /data/media. Ibigay sa media server ang parehong PUID at PGID upang mabasa nito ang files na sinusulat ng arr stack.