SSD Nodes Learn Hosting plans →
Przewodniki Matt ConnorAutor: Matt Connor · Zaktualizowano 2026-08-28

Docker Compose dla stosu arr: Prowlarr, Sonarr, Radarr

Skonfiguruj Prowlarr, Sonarr, Radarr oraz qBittorrent w jednym pliku Docker Compose. Dowiedz się, jak poprawnie ustawić PUID, PGID i wolumeny, aby zapewnić działanie hardlinków.

Co budujesz

Stos Docker Compose typu arr składa się z czterech kontenerów zarządzających biblioteką multimediów: Prowlarr do obsługi indeksatorów, Sonarr do seriali, Radarr do filmów oraz qBittorrent jako klient pobierania. Komunikują się one ze sobą wewnątrz sieci Compose za pomocą nazw usług i współdzielą jedno drzewo katalogów na hoście. Instalacja jest krótka. Elementem decydującym o tym, czy stos będzie działał stabilnie przez lata, czy sprawiał problemy co tydzień, jest struktura wolumenów, dlatego większość tego przewodnika dotyczy właśnie tego zagadnienia.

Stos nie wyszukuje treści za użytkownika. Prowlarr przechowuje dodane do niego indeksatory, a wybór konkretnych usług oraz odpowiedzialność prawna za ich użycie spoczywają na użytkowniku. Ten przewodnik opisuje infrastrukturę: użytkowników, ścieżki, uprawnienia, sieci kontenerów oraz testy weryfikujące poprawność działania.

Jeśli nigdy nie tworzyłeś pliku Compose, przeczytaj najpierw podstawy Docker Compose dla VPS. Ten wpis zakłada, że polecenie docker compose version zwraca już wynik na Twoim serwerze.

Dlaczego hardlinki przestają działać i dlaczego to kluczowa kwestia

Gdy Sonarr kończy pobieranie, importuje plik do biblioteki. Jeśli folder pobierania i folder biblioteki znajdują się w tym samym systemie plików, import odbywa się poprzez hardlink: drugą nazwę wskazującą na te same dane na dysku. Nie zajmuje to dodatkowego miejsca ani czasu. Torrent kontynuuje seedowanie pod starą nazwą, podczas gdy serwer mediów odczytuje plik pod nową.

Jeśli oba foldery znajdują się w różnych systemach plików, jądro nie może utworzyć takiego dowiązania. Sonarr wykonuje wtedy kopiowanie. Sezon o rozmiarze 40 GB zajmuje teraz 80 GB miejsca na dysku i wymaga kilku minut operacji wejścia/wyjścia, a dziennik importu odnotowuje, że hardlink nie powiódł się i plik został skopiowany. Na VPS z ograniczonym miejscem na dysku w ten sposób użytkownicy wyczerpują przestrzeń w ciągu tygodnia.

Oto pułapka. Wewnątrz kontenera bind mount stanowi granicę systemu plików. Zamontuj /mnt/data/torrents jako /downloads oraz /mnt/data/media jako /tv, a mimo że oba znajdują się na jednym dysku hosta, Sonarr widzi dwa oddzielne punkty montowania i odmawia utworzenia dowiązania między nimi. Dokumentacja oficjalnego obrazu LinuxServer.io mówi o tym wprost: używanie oddzielnych ścieżek /downloads oraz /tv uniemożliwia korzystanie z hardlinków.

Rozwiązaniem jest pojedyncze montowanie. Każdy kontener operujący na mediach otrzymuje ten sam, pojedynczy wolumen /mnt/data:/data, a każda używana przez niego ścieżka jest folderem wewnątrz tego wolumenu. Jeden punkt montowania, jeden system plików, działające hardlinki.

Utworzenie użytkownika, grupy i katalogów

Kontenery zapisują pliki przy użyciu numerycznego identyfikatora użytkownika, określonego przez PUID oraz PGID. Należy użyć własnego konta, aby móc odczytywać i edytować te pliki przez SSH bez użycia sudo.

id -u
id -g

Obie wartości zazwyczaj zwracają 1000 na świeżej instalacji Ubuntu VPS. Teraz należy zbudować strukturę katalogów. Należy umieścić ją na dysku, na którym przechowywane są multimedia, i zachować całe drzewo na tym samym nośniku.

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

Przed przejściem dalej należy sprawdzić, czy faktycznie jest to jeden system plików:

df --output=source,target /mnt/data/torrents /mnt/data/media

Obie linie muszą wskazywać to samo urządzenie źródłowe. Dwa różne urządzenia oznaczają, że hardlinki nie będą działać, niezależnie od konfiguracji kontenera.

Katalogi bibliotek zostały celowo nazwane Movies oraz Shows. Jeśli użytkownik korzysta już z Jellyfin jako serwera multimediów, należy zamontować /mnt/data/media w Jellyfin jako /media, dzięki czemu biblioteki znajdą się w /media/Movies oraz /media/Shows, dokładnie tam, gdzie wskazuje wspomniany przewodnik.

Plik środowiskowy

Przechowuj wartości zmienne dla każdego serwera w .env, obok pliku Compose.

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

Utwórz ~/arr/.env:

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

Ustaw TZ na własną strefę czasową, na przykład Europe/Berlin. Aplikacje z rodziny arr planują zadania i oznaczają wpisy w logach zgodnie z tą strefą, więc błędna wartość utrudnia późniejszą analizę dzienników.

Plik Compose

Wpisz ~/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

Cztery elementy w tym pliku wykonują właściwą pracę.

${DATA_ROOT}:/data jest identyczny w trzech kontenerach, które mają dostęp do mediów. Prowlarr go nie otrzymuje, ponieważ Prowlarr nigdy nie otwiera plików multimedialnych.

Każdy port sieciowy jest powiązany z 127.0.0.1, dzięki czemu Docker publikuje go wyłącznie na adresie loopback. Zwykły zapis 8989:8989 opublikowałby go na wszystkich interfejsach, a własne reguły firewalla Dockera przepuściłyby ten ruch bezpośrednio z pominięciem reguły ufw deny. To zachowanie stale zaskakuje użytkowników i zostało wyjaśnione w dlaczego Docker publikuje porty z pominięciem ufw.

Port 6881 jest celowo publikowany na wszystkich interfejsach. Jest to port nasłuchujący dla torrentów i musi być osiągalny dla przychodzących połączeń od peerów. Zezwól na niego za pomocą sudo ufw allow 6881 i zapoznaj się z podstawami firewalla ufw dla VPS, jeśli to polecenie jest nowe.

Katalogi konfiguracyjne są oddzielne dla każdej aplikacji, a współdzielony jest tylko wolumen z mediami. Utwórz je przed pierwszym uruchomieniem, aby ich właścicielem był użytkownik, a nie root:

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

Wszystkie cztery usługi powinny odczytywać running. Według stanu na lipiec 2026 obrazy te są publikowane w lscr.io, a tag latest podąża za bieżącym stabilnym wydaniem, więc przypnij konkretny tag wersji, jeśli chcesz, aby aktualizacje były świadomą decyzją, a nie niespodzianką.

Bezpieczny dostęp do interfejsów webowych

Ponieważ porty są ograniczone do interfejsu loopback, żadna usługa nie jest jeszcze wystawiona na zewnątrz. Przekieruj je przez SSH ze swojej maszyny:

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

Teraz http://127.0.0.1:8989 w przeglądarce pozwala uzyskać dostęp do Sonarr na serwerze. Aby uzyskać stały dostęp, umieść stos za Traefik z certyfikatami TLS dla wielu aplikacji lub połącz się z serwerem przez samodzielnie hostowany VPN WireGuard. Żadna z tych aplikacji nie powinna być wystawiona bezpośrednio do publicznego Internetu, jeśli jedynym zabezpieczeniem jest jej własna strona logowania. Jeśli wybierzesz reverse proxy i wolisz korzystać z jednego konta dla wszystkich czterech interfejsów zamiast zarządzać czterema oddzielnymi logowaniami, Authentik zapewnia samodzielnie hostowane logowanie jednokrotne (SSO), które Traefik może wymuszać przy każdym żądaniu za pomocą mechanizmu forward auth.

qBittorrent generuje losowe hasło administratora przy pierwszym uruchomieniu i wypisuje je w logach kontenera. Odczytaj je, a następnie zmień w interfejsie webowym:

docker compose logs qbittorrent | grep -i password

Jeśli pominiesz tę zmianę, przy każdym restarcie generowane będzie nowe losowe hasło, co zmusi Cię do ponownego sprawdzania logów.

Konfiguracja ścieżek wewnątrz każdej aplikacji

W qBittorrent otwórz Options, następnie Downloads i ustaw domyślną ścieżkę zapisu na /data/torrents. Folder dla nieukończonych pobrań (incomplete-downloads) powinien znajdować się w tym samym drzewie katalogów, na przykład /data/torrents/incomplete. Pobranie, które zakończy się poza /data, nie może zostać powiązane twardym dowiązaniem (hardlink) z biblioteką.

W Sonarr otwórz Settings, następnie Media Management i dodaj folder główny /data/media/Shows. W Radarr folderem głównym jest /data/media/Movies. Są to ścieżki wewnątrz kontenera. Ścieżka hosta /mnt/data/media/Shows zostanie odrzucona, ponieważ z punktu widzenia kontenera ten katalog nie istnieje.

Zarówno w Sonarr, jak i w Radarr otwórz Settings, następnie Download Clients i dodaj qBittorrent. Hostem jest qbittorrent, a portem 8080. Nazwa usługi działa jako nazwa hosta, ponieważ Compose umieszcza wszystkie cztery kontenery w jednej sieci z wewnętrzną usługą DNS (domain name system). Nie używaj tutaj localhost: wewnątrz kontenera Sonarr, localhost to Sonarr.

Pozostaw Remote Path Mappings puste. Ta funkcja służy do tłumaczenia ścieżki zgłaszanej przez klienta pobierania na ścieżkę widoczną dla aplikacji typu arr. Dzięki jednemu wspólnemu punktowi montowania /data, oba kontenery są zgodne co do każdej ścieżki, co stanowi drugi powód, dla którego warto zastosować ten układ.

Łączenie Prowlarr z Sonarr i Radarr

Prowlarr przesyła definicje indeksatorów do pozostałych aplikacji, dzięki czemu konfigurację indeksatora wykonuje się tylko raz. Wymagany jest do tego klucz API (application programming interface) z każdej z nich.

W aplikacji Sonarr otwórz Settings, następnie General i skopiuj klucz API. W Prowlarr otwórz Settings, następnie Apps, dodaj aplikację Sonarr i wypełnij trzy pola. Prowlarr Server to http://prowlarr:9696. Sonarr Server to http://sonarr:8989. API Key to skopiowana wcześniej wartość. Naciśnij Test. Zielony wynik oznacza, że Prowlarr uzyskał połączenie z Sonarr wewnątrz sieci Compose. Powtórz te czynności dla Radarr pod adresem http://radarr:7878.

Czerwony wynik z informacją o odrzuceniu połączenia niemal zawsze oznacza błędną nazwę usługi lub brak prefiksu http://. Potwierdź, czy nazwa jest poprawnie rozwiązywana z wnętrza kontenera:

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

Kod stanu HTTP potwierdza poprawność ścieżki sieciowej. Błąd rozwiązywania nazwy oznacza, że nazwa usługi jest nieprawidłowa.

Nie należy ufać konfiguracji, dopóki nie zostanie sprawdzona liczba dowiązań. Po zaimportowaniu jednego elementu należy porównać pobrany plik z plikiem w bibliotece:

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

Pierwsza liczba to numer inode, a druga to liczba dowiązań. Plik, dla którego utworzono twarde dowiązanie, wykazuje ten sam numer inode w obu lokalizacjach oraz liczbę dowiązań równą 2. Dwa różne numery inode, każdy z liczbą dowiązań równą 1, oznaczają, że Sonarr skopiował plik, a dziennik importu zawiera informację o niepowodzeniu operacji hardlink.

Należy również monitorować użycie dysku. Wartość df -h /mnt/data powinna niemal nie drgnąć podczas importu, ponieważ twarde dowiązanie dodaje jedynie nazwę, a nie kopiuje danych.

Co faktycznie ulega awarii

Błędy uprawnień podczas importu oznaczają, że identyfikator użytkownika kontenera nie ma uprawnień do zapisu w folderze biblioteki. Komunikat to Access to the path ... is denied. Sprawdź za pomocą ls -ln /mnt/data/media, czy identyfikator właściciela jest zgodny z Twoim PUID i pamiętaj, że katalogi wymagają bitu wykonywania, aby kontener mógł uzyskać do nich dostęp.

Pliki, których właścicielem jest root, oznaczają, że kontener uruchomił się, zanim katalog hosta istniał, więc Docker utworzył go jako root. Zatrzymaj stos, chown katalog i uruchom go ponownie.

Usunięcie torrenta z qBittorrent i stwierdzenie, że plik biblioteki zniknął, oznacza, że import był kopią, która została później usunięta, lub że usunięto dane zamiast wpisu torrenta. W przypadku rzeczywistego twardego dowiązania (hardlink), usunięcie jednej nazwy pozostawia drugą nienaruszoną, ponieważ dane są zwalniane dopiero wtedy, gdy licznik dowiązań osiągnie zero.

Dysk zapełniający się szybciej niż dodane media to problem kopii w najbardziej kosztownej postaci. Wykonaj sprawdzenie stat opisane powyżej, zanim dokupisz więcej pamięci masowej.

Wymagania tego stosu względem VPS

Trzy aplikacje arr są lekkie. Odpytują indexery, zapisują dane w niewielkiej bazie SQLite i zmieniają nazwy plików. Serwer z 2 GB pamięci RAM bez problemu obsługuje wszystkie cztery kontenery. Obciążenie pochodzi z innych źródeł. Klient pobierania intensywnie wykorzystuje operacje wejścia i wyjścia dysku podczas obsługi dużych torrentów, a serwer multimediów transkodujący wideo na tym samym serwerze wykorzystuje procesor. Pliki multimedialne należy przechowywać na wolumenie zapewniającym rzeczywistą przepustowość, a jeśli serwer wykonuje inne istotne zadania, należy ustawić limit przepustowości klienta pobierania. Na te dodatkowe zadania należy przeznaczyć osobny zapas zasobów, zamiast zakładać, że dostępna rezerwa wystarczy: samodzielnie hostowany obszar roboczy AFFiNE to kolejne cztery kontenery z bazą danych, a na serwerze z 2 GB pamięci potrzebuje większości pamięci RAM. Nie każda dodatkowa usługa wymaga tak dużych zasobów: przeznaczona do jednego celu aplikacja, taka jak samodzielnie hostowany rejestrator treningów openGym, może bez problemu współdzielić serwer, pod warunkiem skonfigurowania dla niej własnego TLS oraz ustalenia, gdzie znajduje się jej plik bazy danych, zanim zostaną w nim zapisane dane z całego roku treningów. Każdy stos zawierający aplikację webową, bazę danych Postgres i kolejkę zadań obsługiwaną przez proces w tle wymaga zasobów bliższych górnej granicy tego zakresu, dlatego należy zdecydować, czy samodzielnie hostowany system obsługi zgłoszeń Chatwoot powinien działać na tym serwerze, czy na osobnym, zanim ograniczenia zasobów ujawnią się w połowie importu. W przypadku obciążeń skokowych należy zachować jeszcze większą ostrożność, ponieważ o kolizji z importem decyduje szczytowe, a nie średnie obciążenie: jeśli rozważane jest samodzielnie hostowane rozwiązanie OneCLI przydzielające każdej osobie własnego agenta w izolowanym środowisku, należy porównać opublikowane wymagania dotyczące zasobów z faktycznie dostępną pamięcią podczas pracy qBittorrent z pełną wydajnością, a nie z wartością, którą free -h pokazuje na bezczynnej maszynie.

FAQ

Ponieważ z punktu widzenia kontenera źródło i miejsce docelowe znajdują się na różnych systemach plików. Dwa oddzielne punkty montowania (bind mounts), takie jak /downloads i /tv, są traktowane jako odrębne systemy plików, nawet jeśli oba pochodzą z tego samego dysku hosta. Należy zamontować jeden nadrzędny katalog jako /data w każdym kontenerze i umieścić w nim zarówno pobrane pliki, jak i bibliotekę; wtedy tworzenie dowiązań stanie się możliwe. Wynik można potwierdzić za pomocą stat -c '%i %h %n' dla obu plików: powinny mieć ten sam numer inode oraz liczbę dowiązań równą 2.

Jakich wartości PUID i PGID należy użyć?

Należy użyć numerycznego identyfikatora konta hosta, które jest właścicielem drzewa plików multimedialnych; wartości te można uzyskać za pomocą id -u oraz id -g. Na świeżej instalacji Ubuntu VPS zazwyczaj jest to 1000 dla obu parametrów. Każdy kontener w stosie musi używać tej samej pary, w przeciwnym razie jedna aplikacja będzie zapisywać pliki, których druga nie będzie mogła zmodyfikować. Po zmianie wartości należy odtworzyć kontenery za pomocą docker compose up -d --force-recreate i naprawić uprawnienia istniejących plików za pomocą chown -R.

Czy muszę wystawiać te interfejsy WWW do Internetu?

Nie, i nie należy tego robić. Każdy opublikowany port należy powiązać z 127.0.0.1 w pliku Compose, a następnie uzyskiwać dostęp do interfejsów przez tunel SSH, VPN lub reverse proxy, które kończy połączenie TLS (transport layer security) i dodaje własne uwierzytelnianie. Bezpośrednie publikowanie portów jest bardziej ryzykowne, niż się wydaje, ponieważ Docker wprowadza własne reguły zapory sieciowej, przez co reguła ufw deny nie zablokuje tego ruchu.

Gdzie znajdę hasło do qBittorrent?

Obraz LinuxServer.io wypisuje tymczasowe hasło dla użytkownika admin w dzienniku startowym. Należy uruchomić docker compose logs qbittorrent | grep -i password, aby je odczytać, a następnie ustawić stałe hasło w sekcji Options oraz Web UI. Nowe tymczasowe hasło jest generowane przy każdym restarcie, dopóki nie zostanie ustawione własne.

Czy Jellyfin może korzystać z tych samych folderów?

Tak, i na tym polega założenie tego układu. Należy zamontować /mnt/data/media w serwerze multimediów jako /media, dzięki czemu jego biblioteki będą dostępne w /media/Movies i /media/Shows, podczas gdy Sonarr i Radarr będą zapisywać dane w tych samych katalogach przez /data/media. Należy nadać serwerowi multimediów te same wartości PUID i PGID, aby mógł odczytywać pliki zapisane przez stos arr.

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