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 ihn auf allen Schnittstellen veröffentlichen. Die eigenen Firewall-Regeln von Docker würden diesen Datenverkehr dann direkt an einer deny-Regel von ufw vorbeileiten. Dieses Verhalten überrascht viele Benutzer und wird unter Warum Docker Ports direkt an ufw vorbeiverö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. Erlauben Sie ihn mit sudo ufw allow 6881. Wenn dieser Befehl neu für Sie ist, lesen Sie die Grundlagen der ufw-Firewall für einen VPS.
Die Konfigurationsverzeichnisse sind pro Anwendung getrennt. Nur das Medien-Volume wird gemeinsam verwendet. Erstellen Sie die Verzeichnisse vor dem ersten Start. Dadurch 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 Juli 2026 werden diese Images auf lscr.io veröffentlicht. Der Tag latest folgt der aktuellen stabilen Version. Verwenden Sie stattdessen ein Versionstag, wenn Upgrades eine bewusste Entscheidung und keine Überraschung sein sollen.
Sicher auf die Weboberflächen zugreifen
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 nun http://127.0.0.1:8989 in Ihrem Browser aufrufen, erreichen Sie Sonarr auf dem Server. Für einen dauerhaften Zugriff stellen Sie den Stack hinter Traefik mit TLS-Zertifikaten für mehrere Anwendungen, oder greifen Sie über ein selbst gehostetes WireGuard-VPN auf den Server zu. Keine dieser Anwendungen sollte mit ausschließlich ihrer eigenen Anmeldeseite direkt im öffentlichen Internet erreichbar sein. Wenn Sie den Reverse-Proxy-Weg wählen und lieber ein Konto für alle vier Oberflächen als vier separate Anmeldungen für die einzelnen Anwendungen verwalten möchten, bietet 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 gibt es im Container-Log aus. Lesen Sie es aus und ändern Sie es anschließend in der Weboberfläche:
docker compose logs qbittorrent | grep -i passwordWenn Sie die Änderung überspringen, wird bei jedem Neustart ein neues zufälliges Kennwort erzeugt. Dann müssen Sie jedes Mal wieder in den Logs nachsehen.
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, statt Hardlinks zu erstellen?
Weil Quelle und Ziel aus Sicht des Containers auf unterschiedlichen Dateisystemen liegen. Zwei separate Bind-Mounts wie /downloads und /tv gelten auch dann als unterschiedliche Dateisysteme, wenn beide vom selben Host-Datenträger stammen. Mounten Sie in jedem Container ein gemeinsames übergeordnetes Verzeichnis als /data 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 eine Link-Anzahl von 2 aufweisen.
Welche PUID und PGID soll 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 neuen Ubuntu-VPS ist das 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. Korrigieren Sie anschließend die vorhandenen Dateien mit chown -R.
Muss ich diese Weboberflächen im Internet veröffentlichen?
Nein, das sollten Sie nicht tun. Binden Sie jeden veröffentlichten Port in der Compose-Datei an 127.0.0.1. Greifen Sie anschließend über einen SSH-Tunnel, ein VPN oder einen Reverse Proxy darauf zu. Dieser übernimmt die TLS-Terminierung (Transport Layer Security) und fügt eine eigene Authentifizierung hinzu. Eine direkte Veröffentlichung ist problematischer als erwartet. Docker fügt eigene Firewall-Regeln ein. Eine ufw-deny-Regel verhindert diesen Datenverkehr daher nicht.
Wo finde ich das qBittorrent-Passwort?
Das LinuxServer.io-Image schreibt ein temporäres Passwort für den Benutzer admin in das Startprotokoll. 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. Mounten Sie /mnt/data/media in Ihrem Medienserver als /media. Die Bibliotheken liegen dann unter /media/Movies und /media/Shows. Sonarr und Radarr schreiben über /data/media in dieselben Verzeichnisse. Verwenden Sie für den Medienserver dieselben Werte für PUID und PGID. Dadurch kann er die von der arr-Stack geschriebenen Dateien lesen.