SSD Nodes Learn Hosting plans →
Gidsen Matt ConnorDoor Matt Connor · Bijgewerkt 2026-10-06

Arr stack in Docker Compose: de juiste configuratie

Bouw een stabiele stack met Prowlarr, Sonarr, Radarr en qBittorrent. Leer hoe u de juiste PUID, PGID en volume-indeling gebruikt om hardlinks foutloos te laten werken.

Wat u bouwt

Een Docker Compose arr-stack bestaat uit vier containers die een mediabibliotheek beheren: Prowlarr voor indexer-instellingen, Sonarr voor series, Radarr voor films en qBittorrent als downloadclient. Ze communiceren met elkaar via het Compose-netwerk op basis van de servicenaam en delen één mappenstructuur op de host. De installatie is kort. Het onderdeel dat bepaalt of de stack jarenlang stabiel draait of wekelijks problemen geeft, is de volume-indeling; daarom gaat het grootste deel van deze handleiding daarover.

De stack zoekt niet uit zichzelf naar content. Prowlarr bevat de indexers die u zelf toevoegt; welke indexers u gebruikt is uw eigen beslissing en uw juridische verantwoordelijkheid. Deze handleiding behandelt het leidingwerk: gebruikers, paden, rechten, container-netwerken en de controles die aantonen dat alles werkt.

Als u nog nooit een Compose-bestand heeft geschreven, lees dan eerst de basisprincipes van Docker Compose voor een VPS. Dit artikel gaat ervan uit dat docker compose version al output geeft op uw server.

Wanneer Sonarr een download voltooit, importeert het bestand naar uw bibliotheek. Als de downloadmap en de bibliotheekmap zich op hetzelfde bestandssysteem bevinden, is de import een hardlink: een tweede naam die naar dezelfde data op de schijf verwijst. Dit kost geen extra ruimte en geen tijd. De torrent blijft seeden vanaf de oude naam, terwijl uw mediaserver het bestand vanaf de nieuwe naam leest.

Als de twee mappen op verschillende bestandssystemen staan, kan de kernel die link niet maken. Sonarr valt dan terug op een kopie. Een seizoen van 40 GB neemt nu 80 GB schijfruimte in beslag en kost enkele minuten aan input en output. Het importlogboek registreert dat de hardlink is mislukt en dat het bestand in plaats daarvan is gekopieerd. Op een VPS met een vaste schijflimiet is dit de reden waarom gebruikers binnen een week zonder ruimte komen te zitten.

Dit is de valstrik. Binnen een container vormt een bind mount een grens van het bestandssysteem. Mount /mnt/data/torrents als /downloads en /mnt/data/media als /tv; ook al staan beide op dezelfde hostschijf, Sonarr ziet twee afzonderlijke mounts en weigert links tussen deze mappen te maken. De officiële documentatie van de LinuxServer.io-image vermeldt dit expliciet: het gebruik van de afzonderlijke paden /downloads en /tv maakt hardlinks onmogelijk.

De oplossing is één mount. Elke container die media verwerkt, krijgt hetzelfde enkele volume, /mnt/data:/data, en elk pad dat ze gebruiken is een map daarbinnen. Eén mount point, één bestandssysteem, werkende hardlinks.

De gebruiker, de groep en de mappen aanmaken

De containers schrijven bestanden als een numeriek user id, ingesteld via PUID en PGID. Gebruik uw eigen account zodat u deze bestanden via SSH kunt lezen en bewerken zonder sudo.

id -u
id -g

Beide commando's tonen doorgaans 1000 op een nieuwe Ubuntu VPS. Bouw nu de mappenstructuur op. Plaats deze op de schijf waar uw media staat en houd de volledige structuur op die ene schijf.

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

Controleer of het daadwerkelijk één bestandssysteem is voordat u verdergaat:

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

Beide regels moeten hetzelfde bronapparaat tonen. Twee verschillende apparaten betekenen dat hardlinks nooit zullen werken, ongeacht wat u instelt in de containerconfiguratie.

De bibliotheekmappen zijn bewust Movies en Shows genoemd. Als u al Jellyfin als uw mediaserver gebruikt, koppel /mnt/data/media dan in Jellyfin als /media; de bibliotheken komen dan terecht op /media/Movies en /media/Shows, precies waar die handleiding ze plaatst.

Het omgevingsbestand

Houd de waarden die per server verschillen in .env, naast het Compose-bestand.

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

Schrijf ~/arr/.env:

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

Stel TZ in op uw eigen zone, zoals Europe/Berlin. De arr-applicaties plannen taken en voorzien logregels van een tijdstempel in die zone; een onjuiste waarde maakt elk logbestand achteraf onduidelijk.

Het Compose-bestand

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

Vier zaken in dat bestand verrichten het eigenlijke werk.

${DATA_ROOT}:/data is identiek in de drie containers die media benaderen. Prowlarr krijgt dit niet, omdat Prowlarr nooit een mediabestand opent.

Elke webpoort is gebonden aan 127.0.0.1, waardoor Docker deze alleen op het loopback-adres publiceert. Een eenvoudige 8989:8989 zou deze op elke interface publiceren, en de eigen firewallregels van Docker zouden dat verkeer direct langs een ufw deny-regel leiden. Dat gedrag verrast gebruikers constant, en het wordt uitgelegd in waarom Docker poorten direct door ufw heen publiceert.

Poort 6881 wordt bewust op alle interfaces gepubliceerd. Dit is de luisterpoort voor torrents en deze moet bereikbaar zijn voor inkomende peer-verbindingen. Sta deze toe met sudo ufw allow 6881 en lees de basisprincipes van de ufw-firewall voor een VPS als dat commando nieuw voor u is.

De configuratiemappen zijn per applicatie gescheiden en alleen het media-volume wordt gedeeld. Maak deze aan vóór de eerste start, zodat ze eigendom zijn van uw gebruiker in plaats van root:

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

Alle vier de services zouden running moeten lezen. Sinds juli 2026 worden deze images gepubliceerd op lscr.io en de latest-tag volgt de huidige stabiele release. Pin daarom een versie-tag als u wilt dat upgrades een bewuste keuze zijn in plaats van een verrassing.

Veilige toegang tot de webinterfaces

Omdat de poorten op loopback staan, is er nog niets blootgesteld. Forward ze via SSH vanaf uw eigen 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-server

Nu bereikt http://127.0.0.1:8989 in uw browser Sonarr op de server. Voor permanente toegang plaatst u de stack achter Traefik met TLS-certificaten voor meerdere apps, of bereikt u de server via een zelfgehoste WireGuard VPN. Geen van deze applicaties mag direct op het openbare internet staan met enkel een eigen inlogpagina als beveiliging. Als u voor de reverse proxy-route kiest en liever één account voor alle vier de interfaces gebruikt in plaats van vier afzonderlijke inloggegevens te beheren, biedt Authentik zelfgehoste single sign-on die Traefik bij elk verzoek via forward auth kan afdwingen.

qBittorrent genereert bij de eerste start een willekeurig administratorwachtwoord en toont dit in het containerlogboek. Lees dit wachtwoord uit en wijzig het vervolgens in de webinterface:

docker compose logs qbittorrent | grep -i password

Als u deze wijziging overslaat, wordt er bij elke herstart een nieuw willekeurig wachtwoord gegenereerd en zult u telkens opnieuw in de logs moeten kijken.

Stel de paden in binnen elke applicatie

Open in qBittorrent de opties (Options), ga naar Downloads en stel het standaard opslagpad in op /data/torrents. Houd de map voor onvoltooide downloads binnen dezelfde structuur, bijvoorbeeld /data/torrents/incomplete. Een download die buiten /data wordt voltooid, kan niet via een hardlink aan de bibliotheek worden toegevoegd.

Open in Sonarr de instellingen (Settings), ga naar Media Management en voeg de hoofdmap /data/media/Shows toe. In Radarr is de hoofdmap /data/media/Movies. Dit zijn paden binnen de container. Het hostpad /mnt/data/media/Shows wordt geweigerd, omdat die map niet bestaat vanuit het perspectief van de container.

Open in zowel Sonarr als Radarr de instellingen (Settings), ga naar Download Clients en voeg qBittorrent toe. De host is qbittorrent en de poort is 8080. De servicenaam werkt als hostnaam omdat Compose alle vier de containers op één netwerk plaatst met een interne DNS-service (domain name system). Gebruik hier niet localhost: binnen de Sonarr-container is localhost gelijk aan Sonarr.

Laat Remote Path Mappings leeg. Deze functie is bedoeld om een pad dat de downloadclient rapporteert te vertalen naar een pad dat de arr-applicatie kan zien. Met één gedeelde /data-mount zijn beide containers het al eens over elk pad; dit is de tweede reden waarom deze opzet de moeite waard is.

Prowlarr koppelen aan Sonarr en Radarr

Prowlarr pusht indexer-definities naar de andere applicaties, waardoor u een indexer slechts eenmaal hoeft te configureren in plaats van tweemaal. Hiervoor is een API (application programming interface) key van elke applicatie vereist.

Open in Sonarr de instellingen (Settings), ga naar General en kopieer de API key. Open in Prowlarr de instellingen (Settings), ga naar Apps, voeg een Sonarr-applicatie toe en vul de drie velden in. Prowlarr Server is http://prowlarr:9696. Sonarr Server is http://sonarr:8989. API Key is de waarde die u zojuist heeft gekopieerd. Druk op Test. Een groen resultaat betekent dat Prowlarr Sonarr heeft bereikt via het Compose-netwerk. Herhaal dit voor Radarr op http://radarr:7878.

Een rood resultaat met de melding dat de verbinding is geweigerd (connection refused), betekent bijna altijd dat de servicenaam onjuist is of dat het http://-voorvoegsel ontbreekt. Controleer of de naam wordt omgezet vanuit de container:

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

Een HTTP-statuscode bevestigt dat het netwerkpad correct is. Een fout bij de naamomzetting (name resolution error) bevestigt dat de servicenaam onjuist is.

Vertrouw de configuratie pas als u het link-aantal heeft gecontroleerd. Vergelijk na het importeren van één item het gedownloade bestand met het bestand in de bibliotheek:

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

Het eerste getal is de inode en het tweede is het link-aantal. Een bestand dat via een hardlink is gekoppeld, toont in beide gevallen dezelfde inode en een link-aantal van 2. Twee verschillende inodes, elk met een link-aantal van 1, betekenen dat Sonarr het bestand heeft gekopieerd; in het importlogboek zal staan dat de hardlink is mislukt.

Houd ook de schijfactiviteit in de gaten. df -h /mnt/data zou nauwelijks moeten veranderen wanneer een import plaatsvindt, omdat een hardlink enkel een naam toevoegt en geen data kopieert.

Wat er daadwerkelijk misgaat

Rechtenfouten bij het importeren betekenen dat de user id van de container niet naar de bibliotheekmap kan schrijven. De melding is Access to the path ... is denied. Controleer met ls -ln /mnt/data/media of de owner id overeenkomt met uw PUID, en onthoud dat mappen het execute-bit nodig hebben voordat de container ze kan openen.

Bestanden die eigendom lijken te zijn van root betekenen dat de container is gestart voordat de hostmap bestond, waardoor Docker deze als root heeft aangemaakt. Stop de stack, chown de map en start deze opnieuw.

Wanneer u een torrent uit qBittorrent verwijdert en het bibliotheekbestand verdwenen is, betekent dit dat de import een kopie was die later is verwijderd, of dat u de data in plaats van het torrent-item heeft verwijderd. Bij een echte hardlink blijft bij het verwijderen van één naam de andere intact, omdat de data pas wordt vrijgegeven wanneer het link-aantal nul bereikt.

Een schijf die sneller volloopt dan de media die u heeft toegevoegd, is het kopieerprobleem in zijn meest kostbare vorm. Voer de stat-controle hierboven uit voordat u meer opslagruimte aanschaft.

Wat deze stack vereist van een VPS

De drie arr-applicaties zijn lichtgewicht. Ze pollen indexers, schrijven naar een kleine SQLite-database en hernoemen bestanden. Een server met 2 GB RAM draait alle vier de containers zonder problemen. De belasting komt van elders. Een downloadclient verzadigt de schijf-I/O bij grote torrents, en een mediaserver die video transcodeert op dezelfde machine zal de CPU zwaar belasten. Houd media op een volume met voldoende doorvoersnelheid en stel een bandbreedtelimiet in op de downloadclient als de server andere taken uitvoert die voor u van belang zijn. Begroot voor die andere zaken apart in plaats van aan te nemen dat er voldoende ruimte is: een zelfgehoste AFFiNE-werkruimte bestaat uit nog eens vier containers met een database erachter, en op een 2 GB-machine eist deze het grootste deel van dat geheugen op. Niet elke extra service kost zoveel: iets met één enkel doel, zoals een zelfgehoste openGym workout-tracker, deelt de machine probleemloos, zolang u deze voorziet van eigen TLS en weet waar het databasebestand zich bevindt voordat u het een jaar aan trainingsgeschiedenis toevertrouwt. Alles wat een webapplicatie, een Postgres-database en een achtergrond-workerwachtrij bevat, zit dichter bij het AFFiNE-einde van dat spectrum. Bepaal dus of een zelfgehoste Chatwoot-supportdesk op deze server thuishoort of op een eigen server, voordat u halverwege een import de limiet bereikt. Burst-workloads vereisen nog meer voorzichtigheid, omdat hun piekbelasting, en niet het gemiddelde, botst met een import: als u een zelfgehoste OneCLI waarbij elke persoon een eigen gesandboxte agent krijgt overweegt, controleer dan de gepubliceerde systeemvereisten tegen wat er daadwerkelijk vrij is terwijl qBittorrent op volle toeren draait, en niet tegen wat free -h aangeeft op een inactieve machine.

FAQ

Omdat de bron en de bestemming zich vanuit het perspectief van de container op verschillende bestandssystemen bevinden. Twee afzonderlijke bind mounts, zoals /downloads en /tv, worden gezien als twee bestandssystemen, zelfs als ze beide van dezelfde hostschijf komen. Mount één bovenliggende map als /data in elke container, plaats downloads en de bibliotheek daarin, en de link wordt mogelijk. Bevestig het resultaat met stat -c '%i %h %n' op beide bestanden: ze moeten hetzelfde inode-nummer en een link count van 2 hebben.

Welke PUID en PGID moet ik gebruiken?

Gebruik het numerieke ID van het host-account dat eigenaar is van de mediaboom; dit vindt u met id -u en id -g. Op een nieuwe Ubuntu VPS is dit meestal 1000 voor beide. Elke container in de stack moet hetzelfde paar gebruiken, anders schrijft de ene applicatie bestanden die de andere niet kan wijzigen. Nadat u de waarden heeft gewijzigd, moet u de containers opnieuw aanmaken met docker compose up -d --force-recreate en de bestaande bestanden corrigeren met chown -R.

Moet ik deze webinterfaces blootstellen aan het internet?

Nee, en dat moet u ook niet doen. Bind elke gepubliceerde poort aan 127.0.0.1 in het Compose-bestand en benader de interfaces vervolgens via een SSH-tunnel, een VPN of een reverse proxy die TLS (transport layer security) afhandelt en eigen authenticatie toevoegt. Ze direct publiceren is riskanter dan het lijkt, omdat Docker eigen firewallregels invoegt en een ufw deny-regel dat verkeer niet tegenhoudt.

Waar vind ik het wachtwoord voor qBittorrent?

De LinuxServer.io-image print een tijdelijk wachtwoord voor de admin-gebruiker in het opstartlogboek. Voer docker compose logs qbittorrent | grep -i password uit om dit te lezen en stel daarna een permanent wachtwoord in onder Options en Web UI. Bij elke herstart wordt een nieuw tijdelijk wachtwoord gegenereerd totdat u uw eigen wachtwoord instelt.

Kan Jellyfin dezelfde mappen gebruiken?

Ja, en dat is het doel van deze structuur. Mount /mnt/data/media in uw mediaserver als /media; de bibliotheken bevinden zich dan op /media/Movies en /media/Shows, terwijl Sonarr en Radarr via /data/media naar diezelfde mappen schrijven. Geef de mediaserver dezelfde PUID en PGID zodat deze kan lezen wat de arr-stack schrijft.

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