Arr-Stack mit Docker Compose in einer Datei einrichten
Richten Sie Prowlarr, Sonarr, Radarr und qBittorrent auf einem VPS ein. Einheitliche PUID-, PGID- und Volume-Pfade erhalten Hardlinks und vermeiden Berechtigungsfehler.
Was Sie erstellen
Ein Docker-Compose-Arr-Stack besteht aus vier Containern, die eine Medienbibliothek verwalten: Prowlarr für Indexer-Einstellungen, Sonarr für Serien, Radarr für Filme und qBittorrent als Download-Client. Sie kommunizieren im Compose-Netzwerk über den jeweiligen Servicenamen miteinander und verwenden denselben Verzeichnisbaum auf dem Host. Die Installation ist kurz. Ob der Stack jahrelang zuverlässig funktioniert oder Sie jede Woche Probleme verursacht, entscheidet vor allem das Volume-Layout. Deshalb geht es in diesem Leitfaden hauptsächlich darum.
Der Stack findet keine Inhalte für Sie. Prowlarr verwendet die Indexer, die Sie dort hinzufügen. Welche Indexer Sie verwenden, entscheiden Sie selbst und dafür tragen Sie die rechtliche Verantwortung. Dieser Leitfaden behandelt die technische Einrichtung: Benutzer, Pfade, Berechtigungen, Container-Netzwerk und Prüfungen, die den ordnungsgemäßen Betrieb bestätigen.
Wenn Sie noch nie eine Compose-Datei erstellt haben, lesen Sie zuerst die Docker-Compose-Grundlagen für einen VPS. In diesem Beitrag wird vorausgesetzt, dass docker compose version auf Ihrem Server bereits eine Ausgabe erzeugt.
Warum Hardlinks scheitern und warum genau das entscheidend ist
Wenn Sonarr einen Download abgeschlossen hat, importiert es die Datei in Ihre Bibliothek. Befinden sich der Download-Ordner und der Bibliotheksordner auf demselben Dateisystem, ist der Import ein Hardlink: ein zweiter Name, der auf dieselben Daten auf dem Datenträger verweist. Dafür wird weder zusätzlicher Speicherplatz noch zusätzliche Zeit benötigt. Der Torrent wird weiterhin unter dem alten Namen bereitgestellt, während Ihr Medienserver die Datei unter dem neuen Namen liest.
Befinden sich die beiden Ordner auf unterschiedlichen Dateisystemen, kann der Kernel diesen Link nicht erstellen. Sonarr weicht auf eine Kopie aus. Eine Staffel mit 40 GB belegt dann 80 GB Speicherplatz und verursacht mehrere Minuten Ein- und Ausgabe. Das Importprotokoll vermerkt, dass der Hardlink fehlgeschlagen ist und die Datei stattdessen kopiert wurde. Auf einem VPS mit festgelegtem Speicherplatz ist das eine typische Ursache dafür, dass der Speicher innerhalb einer Woche voll läuft.
Hier liegt die Falle. Innerhalb eines Containers bildet ein Bind-Mount eine Dateisystemgrenze. Hängen Sie /mnt/data/torrents als /downloads und /mnt/data/media als /tv ein, sieht Sonarr zwei separate Mounts und verweigert Hardlinks zwischen ihnen, obwohl beide auf demselben Datenträger des Hosts liegen. In der offiziellen Dokumentation des LinuxServer.io-Images wird dies ausdrücklich erklärt: Die Verwendung der separaten Pfade /downloads und /tv verhindert Hardlinks.
Die Lösung ist ein einzelner Mount. Jeder Container, der auf Mediendateien zugreift, erhält dasselbe Volume, /mnt/data:/data, und jeder verwendete Pfad ist ein Ordner darin. Ein Mount-Punkt, ein Dateisystem, funktionierende Hardlinks.
Benutzer, Gruppe und Verzeichnisse anlegen
Die Container schreiben Dateien unter einer numerischen Benutzer-ID, die durch PUID und PGID festgelegt wird. Verwenden Sie Ihr eigenes Konto, damit Sie diese Dateien über SSH ohne sudo lesen und bearbeiten können.
id -u
id -gBeide Befehle geben auf einem frisch eingerichteten Ubuntu-VPS normalerweise 1000 aus. Erstellen Sie nun die Verzeichnisstruktur. Legen Sie sie auf dem Datenträger ab, auf dem Ihre Medien gespeichert sind, und halten Sie die gesamte Struktur auf diesem einen Datenträger.
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/dataPrüfen Sie, ob es sich tatsächlich um ein Dateisystem handelt, bevor Sie fortfahren:
df --output=source,target /mnt/data/torrents /mnt/data/mediaBeide Zeilen müssen dasselbe Quellgerät anzeigen. Bei zwei unterschiedlichen Geräten funktionieren Hardlinks unabhängig von der Einstellung in der Container-Konfiguration nicht.
Die Bibliotheksverzeichnisse heißen absichtlich Movies und Shows. Wenn Sie bereits Jellyfin als Medienserver betreiben, binden Sie /mnt/data/media in Jellyfin als /media ein. Die Bibliotheken werden dann unter /media/Movies und /media/Shows abgelegt, genau wie in dieser Anleitung vorgesehen.
Die Umgebungsdatei
Legen Sie die Werte, die sich je nach Server ändern, in .env neben der Compose-Datei ab.
mkdir -p ~/arr && cd ~/arrSchreiben Sie ~/arr/.env:
PUID=1000
PGID=1000
TZ=Etc/UTC
DATA_ROOT=/mnt/dataSetzen Sie TZ auf Ihre eigene Zone, zum Beispiel Europe/Berlin. Die arr-Anwendungen planen Aufgaben und versehen Logzeilen mit Zeitstempeln in dieser Zone. Ein falscher Wert macht später alle Logs unübersichtlich.
Die Compose-Datei
Schreiben Sie ~/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-stoppedVier Dinge in dieser Datei übernehmen die eigentliche Arbeit.
${DATA_ROOT}:/data ist in den drei Containern identisch, die auf Mediendateien zugreifen. Prowlarr erhält diese Variable nicht, weil Prowlarr niemals eine Mediendatei öffnet.
Jeder Webport ist an 127.0.0.1 gebunden. Docker veröffentlicht ihn dadurch nur auf der Loopback-Adresse. Ein einfaches 8989:8989 würde den Port auf allen Schnittstellen veröffentlichen. Die eigenen Firewall-Regeln von Docker würden diesen Datenverkehr dann direkt an einer ufw-deny-Regel vorbeileiten. Dieses Verhalten überrascht viele Benutzer regelmäßig. Es wird unter warum Docker Ports direkt an ufw vorbei veröffentlicht erklärt.
Port 6881 wird absichtlich auf allen Schnittstellen veröffentlicht. Das ist der Listening-Port für Torrents. Er muss für eingehende Peer-Verbindungen erreichbar sein. Dasselbe Docker-Verhalten gilt auch hier. Sie benötigen dafür daher keine ufw-Regel. Eine zusätzliche Regel würde nichts ändern. Die im Oktober 2026 geprüfte Docker-Dokumentation besagt, dass Datenverkehr zu einem veröffentlichten Port weitergeleitet wird, bevor er die von ufw verwendeten Chains erreicht. Daher hat sudo ufw allow 6881 auf diesen Port keinen Einfluss. Der Port ist ab dem Start des Containers aus dem Internet erreichbar. Auch ein ufw-deny würde ihn nicht schließen. Wenn Sie die möglichen Verbindungen irgendwann einschränken müssen, verweist die Docker-Dokumentation auf Regeln in der DOCKER-USER-Chain und nicht auf ufw. Verwenden Sie ufw für Dienste, die nicht in Docker laufen, beispielsweise SSH. Wenn dieses Tool für Sie neu ist, lesen Sie die ufw-Grundlagen für einen VPS.
Die Konfigurationsverzeichnisse sind pro Anwendung getrennt. Nur das Medienvolume wird gemeinsam verwendet. Erstellen Sie die Verzeichnisse vor dem ersten Start. Dann gehören sie Ihrem Benutzer und nicht root:
mkdir -p ~/arr/config/prowlarr ~/arr/config/sonarr ~/arr/config/radarr ~/arr/config/qbittorrent
docker compose up -d
docker compose psAlle vier Dienste sollten running lesen. Im Oktober 2026 werden diese Images auf lscr.io veröffentlicht. Der Tag latest folgt dem aktuellen Stable-Release. Verwenden Sie stattdessen einen Versions-Tag, wenn Upgrades eine bewusste Entscheidung und keine Überraschung sein sollen.
Weboberflächen sicher erreichen
Da die Ports an loopback gebunden sind, ist noch nichts öffentlich erreichbar. Leiten Sie sie von Ihrem eigenen Rechner per SSH weiter:
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-serverWenn Sie jetzt http://127.0.0.1:8989 in Ihrem Browser öffnen, erreichen Sie Sonarr auf dem Server. Für einen dauerhaften Zugriff können Sie den Stack hinter Traefik mit TLS-Zertifikaten für mehrere Anwendungen betreiben oder den Server über ein selbst gehostetes WireGuard-VPN erreichen. Ein VPN kann diese Ports jedoch nicht automatisch erreichen. Die im Oktober 2026 geprüfte Dokumentation von Docker besagt, dass bei der Veröffentlichung eines Ports an eine localhost-Adresse nur der Docker-Host darauf zugreifen kann. Um das VPN zu verwenden, lassen Sie entweder den SSH-Tunnel darüber weiterlaufen oder ändern Sie die Bindings in der Compose-Datei von 127.0.0.1 auf die eigene Adresse des Servers an der VPN-Schnittstelle. Keine dieser Anwendungen sollte mit nur der eigenen Anmeldeseite als Schutz direkt im öffentlichen Internet betrieben werden. Wenn Sie den Reverse-Proxy-Weg wählen und lieber ein gemeinsames Konto für alle vier Oberflächen statt vier separater Anmeldungen verwalten möchten, ermöglicht Authentik selbst gehostetes Single Sign-on, das Traefik mit Forward Auth bei jeder Anfrage erzwingen kann.
qBittorrent erzeugt beim ersten Start ein zufälliges Administratorkennwort und schreibt es in das Container-Log. Lesen Sie es aus und ändern Sie es anschließend in der Weboberfläche:
docker compose logs qbittorrent | grep -i passwordWenn Sie das Kennwort nicht ändern, wird bei jedem Neustart ein neues zufälliges Kennwort erzeugt. Dann müssen Sie jedes Mal wieder in den Logs danach suchen.
Pfade innerhalb der einzelnen Anwendungen festlegen
Öffnen Sie in qBittorrent Options und anschließend Downloads. Legen Sie den Standardspeicherpfad auf /data/torrents fest. Der Ordner für unvollständige Downloads muss innerhalb desselben Verzeichnisbaums liegen, zum Beispiel unter /data/torrents/incomplete. Ein Download, der außerhalb von /data abgeschlossen wird, kann nicht per Hardlink in die Bibliothek eingebunden werden.
Öffnen Sie in Sonarr Settings und anschließend Media Management. Fügen Sie den Stammordner /data/media/Shows hinzu. In Radarr lautet der Stammordner /data/media/Movies. Dies sind Pfade innerhalb des Containers. Der Hostpfad /mnt/data/media/Shows wird abgelehnt, weil dieses Verzeichnis aus Sicht des Containers nicht vorhanden ist.
Öffnen Sie in Sonarr und Radarr jeweils Settings und anschließend Download Clients. Fügen Sie qBittorrent hinzu. Der Host ist qbittorrent und der Port ist 8080. Der Dienstname funktioniert als Hostname, weil Compose alle vier Container in einem Netzwerk mit einem internen DNS-Dienst (domain name system) anordnet. Verwenden Sie hier nicht localhost: Innerhalb des Sonarr-Containers ist localhost Sonarr.
Lassen Sie Remote Path Mappings leer. Mit dieser Funktion wird ein Pfad, den der Download-Client meldet, in einen Pfad übersetzt, den die arr-Anwendung sehen kann. Bei einem gemeinsam verwendeten /data-Mount stimmen beide Container bereits bei allen Pfaden überein. Das ist der zweite Grund, warum sich dieser Aufbau lohnt.
Prowlarr mit Sonarr und Radarr verbinden
Prowlarr überträgt Indexer-Definitionen an die anderen Anwendungen. Dadurch konfigurieren Sie einen Indexer nur einmal statt zweimal. Dafür benötigt Prowlarr jeweils einen API-Schlüssel (Application Programming Interface).
Öffnen Sie in Sonarr Settings und anschließend General. Kopieren Sie den API-Schlüssel. Öffnen Sie in Prowlarr Settings und anschließend Apps. Fügen Sie eine Sonarr-Anwendung hinzu und füllen Sie drei Felder aus. Prowlarr Server ist http://prowlarr:9696. Sonarr Server ist http://sonarr:8989. API Key ist der kopierte Wert. Klicken Sie auf Test. Ein grünes Ergebnis bedeutet, dass Prowlarr Sonarr über das Compose-Netzwerk erreicht hat. Wiederholen Sie den Vorgang für Radarr unter http://radarr:7878.
Ein rotes Ergebnis mit der Meldung, dass die Verbindung abgelehnt wurde, weist fast immer auf einen falschen Dienstnamen oder ein fehlendes http://-Präfix hin. Prüfen Sie, ob der Name innerhalb des Containers aufgelöst wird:
docker compose exec prowlarr curl -sS -o /dev/null -w '%{http_code}\n' http://sonarr:8989Ein HTTP-Statuscode bestätigt, dass der Netzwerkpfad funktioniert. Ein Fehler bei der Namensauflösung bestätigt, dass der Dienstname falsch ist.
Nachweisen, dass tatsächlich Hardlinks erstellt werden
Vertrauen Sie der Einrichtung erst, wenn Sie die Link-Anzahl geprüft haben. Nachdem ein Element importiert wurde, vergleichen Sie die heruntergeladene Datei mit der Datei in der Bibliothek:
stat -c '%i %h %n' /mnt/data/torrents/tv/*/*.mkv
stat -c '%i %h %n' /mnt/data/media/Shows/*/*/*.mkvDie erste Zahl ist die Inode und die zweite Zahl ist die Link-Anzahl. Bei einer Datei mit Hardlink ist die Inode an beiden Stellen identisch und die Link-Anzahl beträgt 2. Zwei unterschiedliche Inodes mit jeweils einer Link-Anzahl von 1 bedeuten, dass Sonarr die Datei kopiert hat. Im Importprotokoll wird dann angezeigt, dass der Hardlink fehlgeschlagen ist.
Beobachten Sie außerdem den Speicherplatz. df -h /mnt/data sollte sich während eines Imports kaum verändern, weil ein Hardlink einen Namen, aber keine Daten hinzufügt.
Was tatsächlich fehlschlägt
Berechtigungsfehler beim Import bedeuten, dass die Benutzer-ID des Containers nicht in den Bibliotheksordner schreiben kann. Die Meldung lautet Access to the path ... is denied. Prüfen Sie mit ls -ln /mnt/data/media, ob die Eigentümer-ID Ihrer PUID entspricht. Verzeichnisse benötigen außerdem das Execute-Bit, damit der Container sie betreten kann.
Dateien, die scheinbar root gehören, weisen darauf hin, dass der Container gestartet wurde, bevor das Host-Verzeichnis existierte. Docker hat es daher als root angelegt. Stoppen Sie den Stack, chown Sie das Verzeichnis, und starten Sie ihn erneut.
Wenn Sie einen Torrent in qBittorrent löschen und die Bibliotheksdatei anschließend fehlt, wurde beim Import eine Kopie erstellt, die später entfernt wurde. Möglich ist auch, dass Sie die Daten statt des Torrent-Eintrags gelöscht haben. Bei einem echten Hardlink bleibt der andere Name erhalten, wenn ein Name entfernt wird. Die Daten werden erst freigegeben, wenn die Link-Anzahl null erreicht.
Wenn sich ein Datenträger schneller füllt als durch die hinzugefügten Medien zu erwarten wäre, handelt es sich um das kostenintensivste Problem der Kopiermethode. Führen Sie zunächst die oben beschriebene stat-Prüfung aus, bevor Sie zusätzlichen Speicher kaufen.
Was dieser Stack von einem VPS benötigt
Die drei arr-Anwendungen sind ressourcenschonend. Sie fragen Indexer ab, schreiben in eine kleine SQLite-Datenbank und benennen Dateien um. Ein Server mit 2 GB RAM kann alle vier Container problemlos ausführen. Die Last entsteht an anderer Stelle. Ein Download-Client kann bei großen Torrents die Datenträger-Ein- und -Ausgabe auslasten. Ein auf demselben System laufender Medienserver beansprucht beim Transkodieren von Video die CPU. Legen Sie die Mediendateien auf einem Volume mit realem Datendurchsatz ab. Setzen Sie eine Bandbreitenbegrenzung für den Download-Client, wenn der Server noch andere wichtige Aufgaben übernimmt.
Planen Sie diese zusätzlichen Aufgaben separat ein. Gehen Sie nicht automatisch davon aus, dass ausreichende Reserven vorhanden sind: ein selbst gehosteter AFFiNE-Arbeitsbereich besteht aus weiteren vier Containern mit einer dahinterliegenden Datenbank. Auf einem System mit 2 GB benötigt er den größten Teil des Arbeitsspeichers für sich. Nicht jeder zusätzliche Dienst benötigt so viele Ressourcen: Eine Anwendung mit nur einem Zweck wie ein selbst gehosteter openGym-Trainingstracker kann problemlos auf demselben System laufen. Geben Sie ihr jedoch eine eigene TLS-Konfiguration. Stellen Sie außerdem sicher, dass Sie wissen, wo ihre Datenbankdatei liegt, bevor Sie ihr die Trainingshistorie eines ganzen Jahres anvertrauen.
Alles, was eine Webanwendung, eine Postgres-Datenbank und eine Hintergrund-Worker-Warteschlange umfasst, liegt eher am oberen Ende dieser Spanne. Entscheiden Sie daher, ob ein selbst gehosteter Chatwoot-Supportdesk auf diesem Server oder auf einem eigenen System laufen soll. Warten Sie damit nicht, bis die Begrenzung mitten in einem Import erreicht wird. Bei stoßartigen Workloads ist noch mehr Vorsicht erforderlich. Für einen Import ist der Spitzenwert entscheidend, nicht der Durchschnittswert. Wenn Sie einen selbst gehosteten OneCLI-Dienst in Betracht ziehen, der jeder Person einen eigenen isolierten Agenten bereitstellt, vergleichen Sie dessen veröffentlichte Größenempfehlungen mit dem tatsächlich verfügbaren Speicher, während qBittorrent mit voller Leistung läuft. Vergleichen Sie sie nicht mit dem Wert, den free -h auf einem inaktiven System anzeigt.
FAQ
Warum kopiert Sonarr Dateien, anstatt Hardlinks zu erstellen?
Weil sich Quelle und Ziel aus Sicht des Containers in unterschiedlichen Dateisystemen befinden. Zwei separate Bind-Mounts wie /downloads und /tv gelten auch dann als unterschiedliche Dateisysteme, wenn beide vom selben Host-Datenträger stammen. Binden Sie in jedem Container ein gemeinsames übergeordnetes Verzeichnis als /data ein und legen Sie Downloads und Bibliothek darunter ab. Dann kann der Link erstellt werden. Prüfen Sie das Ergebnis mit stat -c '%i %h %n' für beide Dateien. Beide müssen dieselbe Inode und einen Link-Zähler von 2 aufweisen.
Welche PUID und PGID sollte ich verwenden?
Verwenden Sie die numerische ID des Hostkontos, dem der Medienverzeichnisbaum gehört. Ermitteln Sie sie mit id -u und id -g. Auf einem frisch eingerichteten Ubuntu VPS ist sie für beide Werte normalerweise 1000. Jeder Container im Stack muss dasselbe Wertepaar verwenden. Andernfalls schreibt eine Anwendung Dateien, die eine andere Anwendung nicht ändern kann. Erstellen Sie die Container nach einer Änderung der Werte mit docker compose up -d --force-recreate neu und korrigieren Sie die vorhandenen Dateien mit chown -R.
Muss ich diese Weboberflächen im Internet veröffentlichen?
Nein, das sollten Sie nicht. Binden Sie jeden veröffentlichten Port in der Compose-Datei an 127.0.0.1. Greifen Sie anschließend über einen SSH-Tunnel oder über einen Reverse Proxy auf dem Server auf die Oberflächen zu. Dieser beendet TLS (Transport Layer Security) und fügt eine eigene Authentifizierung hinzu. Ein VPN allein kann keinen an 127.0.0.1 gebundenen Port erreichen, weil Docker ihn nur auf dem Host selbst erreichbar macht. Wenn Sie den direkten Zugriff über ein VPN benötigen, veröffentlichen Sie die Ports stattdessen auf der VPN-Adresse des Servers. Die direkte Veröffentlichung auf der öffentlichen Schnittstelle ist riskanter als erwartet. Docker fügt eigene Firewall-Regeln ein, und eine ufw-deny-Regel blockiert diesen Datenverkehr nicht.
Wo finde ich das qBittorrent-Passwort?
Das LinuxServer.io-Image gibt ein temporäres Passwort für den Benutzer admin im Startprotokoll aus. Lesen Sie es mit docker compose logs qbittorrent | grep -i password aus. Legen Sie anschließend unter Options und Web UI ein dauerhaftes Passwort fest. Bei jedem Neustart wird ein neues temporäres Passwort erzeugt, bis Sie ein eigenes festlegen.
Kann Jellyfin dieselben Verzeichnisse verwenden?
Ja, genau dafür ist dieses Layout vorgesehen. Binden Sie /mnt/data/media in Ihren Medienserver als /media ein. Seine Bibliotheken befinden sich dann unter /media/Movies und /media/Shows, während Sonarr und Radarr über /data/media in dieselben Verzeichnisse schreiben. Geben Sie dem Medienserver dieselben PUID und PGID, damit er die vom arr-Stack geschriebenen Dateien lesen kann.