SSD Nodes Learn 8GB RAM — $66/jaar
Gidsen Matt ConnorDoor Matt Connor · Bijgewerkt 2026-08-02

Arr-stack met Docker Compose: één bestand op een VPS

Draai Prowlarr, Sonarr, Radarr en qBittorrent met één Docker Compose-bestand. Gebruik dezelfde PUID, PGID en volumelay-out zodat hardlinks blijven werken.

Wat u bouwt

Een Docker Compose arr-stack bestaat uit vier containers die een medial bibliotheek beheren: Prowlarr voor indexerinstellingen, Sonarr voor series, Radarr voor films en qBittorrent als downloadclient. Ze communiceren met elkaar via het Compose-netwerk met de servicenaam en delen één mappenstructuur op de host. De installatie is kort. De volumelay-out bepaalt of de stack jarenlang goed blijft werken of u elke week problemen geeft. Daarom gaat het grootste deel van deze handleiding daarover.

De stack zoekt geen content voor u. Prowlarr bevat de indexers die u eraan toevoegt. Welke indexers u gebruikt, is uw beslissing en uw wettelijke verantwoordelijkheid. Deze handleiding behandelt de technische basis: gebruikers, paden, machtigingen, containernetwerken en controles waarmee u aantoont dat alles werkt.

Als u nog nooit een Compose-bestand hebt geschreven, lees dan eerst de basis van Docker Compose voor een VPS. In deze handleiding wordt ervan uitgegaan dat docker compose version al iets op uw server weergeeft.

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

Als de twee mappen zich op verschillende bestandssystemen bevinden, kan de kernel die link niet maken. Sonarr valt dan terug op kopiëren. Een seizoen van 40 GB gebruikt nu 80 GB schijfruimte en vereist enkele minuten invoer en uitvoer. In het importlogboek staat vervolgens dat de hardlink is mislukt en dat het bestand in plaats daarvan is gekopieerd. Op een VPS met een vaste schijfruimtebeperking is dat de manier waarop de beschikbare ruimte binnen een week opraakt.

Hier zit de valkuil. In een container vormt een bind mount een grens tussen bestandssystemen. Mount /mnt/data/torrents als /downloads en /mnt/data/media als /tv. Hoewel beide mappen op één hostschijf staan, ziet Sonarr twee afzonderlijke mounts en weigert het links tussen deze mounts te maken. De officiële documentatie van de LinuxServer.io-image vermeldt dit rechtstreeks: door de afzonderlijke paden /downloads en /tv te gebruiken, verliest u de mogelijkheid om hardlinks te maken.

De oplossing is één mount. Elke container die media gebruikt, krijgt hetzelfde enkele volume, /mnt/data:/data. Elk pad dat de containers gebruiken, is een map binnen dat volume. Eén mountpunt, één bestandssysteem en werkende hardlinks.

De gebruiker, de groep en de mappen maken

De containers schrijven bestanden weg met een numerieke gebruikers-id die wordt ingesteld door PUID en PGID. Gebruik uw eigen account, zodat u deze bestanden via SSH kunt lezen en bewerken zonder sudo.

id -u
id -g

Beide opdrachten geven op een nieuwe Ubuntu VPS meestal 1000 weer. Maak nu de mappenstructuur. Plaats deze op de schijf waarop uw media staan 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 voordat u verdergaat of dit inderdaad één bestandssysteem is:

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

Beide regels moeten hetzelfde bronapparaat tonen. Als er twee verschillende apparaten worden weergegeven, werken hardlinks nooit, ongeacht wat u in de containerconfiguratie instelt.

De bibliotheekmappen heten bewust Movies en Shows. Als u Jellyfin al als mediaserver gebruikt, koppelt u /mnt/data/media in Jellyfin aan als /media. De bibliotheken komen dan terecht in /media/Movies en /media/Shows, precies zoals in die handleiding.

Het omgevingsbestand

Bewaar 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 alle logboeken later 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 onderdelen in dat bestand voeren de belangrijkste taken uit.

${DATA_ROOT}:/data is identiek in de drie containers die mediabestanden gebruiken. Prowlarr krijgt deze variabele niet, omdat Prowlarr nooit een mediabestand opent.

Elke webpoort is gebonden aan 127.0.0.1. Docker publiceert deze poorten daardoor alleen op het loopbackadres. Met een gewone 8989:8989 zou Docker de poort op elke interface publiceren. De eigen firewallregels van Docker zouden dit verkeer dan rechtstreeks langs een ufw deny-regel sturen. Dit gedrag is vaak verrassend. Zie waarom Docker poorten rechtstreeks door ufw publiceert voor een uitleg.

Poort 6881 wordt bewust op alle interfaces gepubliceerd. Dit is de luisterpoort voor torrents. Deze poort moet bereikbaar zijn voor inkomende peerverbindingen. Sta de poort toe met sudo ufw allow 6881. Lees de basisprincipes van de ufw-firewall voor een VPS als dit commando nieuw voor u is.

De configuratiemappen zijn per toepassing gescheiden. Alleen het mediavolume wordt gedeeld. Maak de mappen aan voordat u de services voor het eerst start. Zo zijn ze eigendom van uw gebruiker en niet 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 services moeten running lezen. In juli 2026 worden deze images gepubliceerd op lscr.io. De tag latest volgt de huidige stabiele release. Gebruik daarom een specifieke versie-tag als upgrades een bewuste keuze moeten zijn en geen verrassing.

Veilige toegang tot de webinterfaces

Omdat de poorten op loopback luisteren, is er nog niets openbaar toegankelijk. Stuur ze vanaf uw eigen machine door via SSH:

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 maakt u verbinding met de server via een WireGuard-VPN die u zelf beheert. Geen van deze toepassingen hoort rechtstreeks op het openbare internet te staan met alleen de eigen aanmeldingspagina als beveiliging.

qBittorrent genereert bij de eerste start een willekeurig beheerderswachtwoord en schrijft dit naar het containerlog. Lees het wachtwoord uit en wijzig het vervolgens in de webinterface:

docker compose logs qbittorrent | grep -i password

Als u dit overslaat, wordt bij elke herstart een nieuw willekeurig wachtwoord gegenereerd. U moet het wachtwoord dan telkens opnieuw in de logs opzoeken.

Stel de paden in elke applicatie in

Open in qBittorrent Options en vervolgens Downloads. Stel het standaardpad voor opslaan in op /data/torrents. Houd de map voor onvoltooide downloads binnen dezelfde structuur, bijvoorbeeld /data/torrents/incomplete. Een download die ergens buiten /data wordt voltooid, kan niet als hardlink aan de bibliotheek worden toegevoegd.

Open in Sonarr Settings en vervolgens Media Management. 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 vanuit het perspectief van de container niet bestaat.

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

Laat Remote Path Mappings leeg. Deze functie vertaalt een pad dat de downloadclient rapporteert naar een pad dat de arr-applicatie kan zien. Met één gedeelde /data-mount komen beide containers al overeen met elk pad. Dat is de tweede reden waarom deze indeling de extra inspanning waard is.

Prowlarr verbinden met Sonarr en Radarr

Prowlarr stuurt indexerdefinities naar de andere toepassingen. U configureert een indexer daardoor één keer in plaats van twee keer. Hiervoor is een API (application programming interface)-sleutel van elke toepassing nodig.

Open in Sonarr Settings en vervolgens General. Kopieer de API-sleutel. Open in Prowlarr Settings en vervolgens Apps. Voeg een Sonarr-toepassing toe en vul drie velden in. Prowlarr Server is http://prowlarr:9696. Sonarr Server is http://sonarr:8989. API Key is de waarde die u hebt gekopieerd. Klik op Test. Een groen resultaat betekent dat Prowlarr Sonarr via het Compose-netwerk heeft bereikt. Herhaal dit voor Radarr op http://radarr:7878.

Een rood resultaat met de melding dat de verbinding is geweigerd, betekent vrijwel altijd dat de servicenaam onjuist is of dat het voorvoegsel http:// ontbreekt. Controleer of de naam vanuit de container wordt opgelost:

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

Een HTTP-statuscode bewijst dat het netwerkpad werkt. Een fout bij de naamresolutie bewijst dat de servicenaam onjuist is.

Vertrouw de configuratie pas nadat u het aantal links hebt gecontroleerd. Nadat één item is geïmporteerd, vergelijkt u het gedownloade bestand met het bibliotheekbestand:

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 getal is het aantal links. Bij een bestand met een hardlink is de inode op beide locaties hetzelfde en is het aantal links 2. Twee verschillende inodes, elk met een aantal links van 1, betekenen dat Sonarr het bestand heeft gekopieerd. In het importlogboek staat dan dat de hardlink is mislukt.

Controleer ook het schijfgebruik. df -h /mnt/data hoort nauwelijks te veranderen wanneer er een import plaatsvindt, omdat een hardlink alleen een naam toevoegt en geen gegevens.

Wat er daadwerkelijk misgaat

Permissiefouten bij het importeren betekenen dat de gebruikers-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 eigenaar-id overeenkomt met uw PUID. Houd er rekening mee 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. Docker heeft de map daarom als root aangemaakt. Stop de stack, chown de map en start de stack opnieuw.

Als u een torrent uit qBittorrent verwijdert en het bibliotheekbestand verdwenen blijkt te zijn, was het importeren een kopie die later is verwijderd. Het kan ook zijn dat u de gegevens hebt verwijderd in plaats van alleen de torrentvermelding. Bij een echte hardlink blijft de andere naam intact wanneer u één naam verwijdert. De gegevens worden pas vrijgegeven wanneer het aantal links nul bereikt.

Een schijf die sneller volloopt dan de hoeveelheid media die u hebt toegevoegd, is de meest kostbare vorm van het kopieerprobleem. Voer de stat-controle hierboven uit voordat u meer opslag aanschaft.

Wat deze stack van een VPS nodig heeft

De drie arr-applicaties zijn licht. Ze vragen indexers op, schrijven naar een kleine SQLite-database en hernoemen bestanden. Een server met 2 GB RAM draait alle vier containers zonder problemen. De belasting komt ergens anders vandaan. Een downloadclient kan de schijf-invoer en -uitvoer bij grote torrents volledig benutten. Een mediaserver die video transcodeert op dezelfde server kan de CPU volledig belasten. Bewaar media op een volume met een hoge werkelijke doorvoersnelheid. Stel een bandbreedtelimiet in op de downloadclient als de server ook andere belangrijke taken uitvoert.

FAQ

Dit komt doordat de bron en de bestemming vanuit het perspectief van de container op verschillende bestandssystemen staan. Twee afzonderlijke bind mounts, zoals /downloads en /tv, zijn twee bestandssystemen, ook als beide afkomstig zijn van één hostschijf. Koppel in elke container één bovenliggende map als /data, plaats de downloads en de bibliotheek daarin, en de koppeling wordt mogelijk. Bevestig het resultaat met stat -c '%i %h %n' op beide bestanden: dezelfde inode en een koppeltelling van 2.

Welke PUID en PGID moet ik gebruiken?

Gebruik de numerieke id van het hostaccount dat eigenaar is van de mediaboom. U haalt deze op met id -u en id -g. Op een nieuwe Ubuntu VPS is dit doorgaans voor beide 1000. Elke container in de stack moet hetzelfde paar gebruiken. Anders schrijft de ene toepassing bestanden die een andere toepassing niet kan wijzigen. Maak de containers opnieuw aan met docker compose up -d --force-recreate nadat u de waarden hebt gewijzigd en corrigeer de bestaande bestanden met chown -R.

Moet ik deze webinterfaces beschikbaar maken op internet?

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

Waar vind ik het qBittorrent-wachtwoord?

De LinuxServer.io-image schrijft in het opstartlogboek een tijdelijk wachtwoord voor de gebruiker admin. 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, dat is het doel van deze indeling. Koppel /mnt/data/media in uw mediaserver als /media en de bibliotheken bevinden zich op /media/Movies en /media/Shows, terwijl Sonarr en Radarr via /data/media naar dezelfde 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