Stack arr : Docker Compose pour Prowlarr, Sonarr et Radarr
Déployez Prowlarr, Sonarr, Radarr et qBittorrent avec un seul fichier Docker Compose, un PUID, un PGID et des volumes partagés pour préserver les hardlinks.
Ce que vous allez construire
Une stack arr Docker Compose comprend quatre conteneurs qui gèrent une bibliothèque multimédia : Prowlarr pour la configuration des indexers, Sonarr pour les séries, Radarr pour les films et qBittorrent comme client de téléchargement. Ils communiquent entre eux sur le réseau Compose via le nom du service et partagent une arborescence de dossiers sur l’hôte. L’installation est rapide. La disposition des volumes détermine si la stack fonctionnera pendant des années ou vous posera des problèmes chaque semaine. C’est pourquoi l’essentiel de ce guide lui est consacré.
La stack ne recherche pas de contenu à votre place. Prowlarr contient les indexers que vous y ajoutez. Le choix des indexers vous appartient et relève de votre responsabilité juridique. Ce guide traite de la configuration technique : utilisateurs, chemins, permissions, réseau des conteneurs et vérifications qui confirment que tout fonctionne.
Si vous n’avez jamais écrit de fichier Compose, lisez d’abord les bases de Docker Compose pour un VPS. Ce guide suppose que docker compose version affiche déjà quelque chose sur votre serveur.
Pourquoi les hardlinks échouent et pourquoi tout se joue là
Lorsque Sonarr termine un téléchargement, il importe le fichier dans votre bibliothèque. Si le dossier de téléchargement et le dossier de la bibliothèque se trouvent sur le même système de fichiers, l’import crée un hardlink : un deuxième nom qui pointe vers les mêmes données sur le disque. Cela ne consomme pas d’espace supplémentaire et ne prend pas de temps. Le torrent continue le seeding depuis l’ancien nom, tandis que votre media server lit le nouveau.
Si les deux dossiers se trouvent sur des systèmes de fichiers différents, le kernel ne peut pas créer ce lien. Sonarr utilise alors une copie. Une saison de 40 GB occupe désormais 80 GB sur le disque et nécessite plusieurs minutes d’entrées-sorties. Le journal d’import indique que la création du hardlink a échoué et que le fichier a été copié à la place. Sur un VPS avec un quota disque fixe, c’est ainsi que l’espace disponible peut être épuisé en une semaine.
Voici le piège. Dans un container, un bind mount constitue une limite de système de fichiers. Montez /mnt/data/torrents comme /downloads et /mnt/data/media comme /tv : même si les deux se trouvent sur un seul disque de l’hôte, Sonarr voit deux mounts distincts et refuse de créer un lien entre eux. La documentation officielle de l’image LinuxServer.io l’indique clairement : utiliser séparément les chemins /downloads et /tv empêche la création de hardlinks.
La solution consiste à utiliser un seul mount. Chaque container qui accède aux media reçoit le même volume unique, /mnt/data:/data, et chaque chemin utilisé correspond à un dossier situé à l’intérieur de ce volume. Un seul point de montage, un seul système de fichiers, des hardlinks fonctionnels.
Créer l’utilisateur, le groupe et les dossiers
Les conteneurs écrivent les fichiers avec un identifiant utilisateur numérique, défini par PUID et PGID. Utilisez votre propre compte afin de pouvoir lire et modifier ces fichiers via SSH sans sudo.
id -u
id -gLes deux commandes affichent généralement 1000 sur un VPS Ubuntu fraîchement installé. Créez maintenant l’arborescence. Placez-la sur le disque qui contient vos médias et conservez toute l’arborescence sur ce même disque.
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/dataVérifiez qu’il s’agit bien d’un seul système de fichiers avant de continuer :
df --output=source,target /mnt/data/torrents /mnt/data/mediaLes deux lignes doivent afficher le même périphérique source. Si les périphériques diffèrent, les hard links ne fonctionneront jamais, quels que soient les paramètres de la configuration du conteneur.
Les dossiers de la bibliothèque sont nommés Movies et Shows volontairement. Si vous utilisez déjà Jellyfin comme serveur multimédia, montez /mnt/data/media dans Jellyfin sous /media ; ses bibliothèques seront alors placées dans /media/Movies et /media/Shows, exactement comme l’indique ce guide.
Le fichier d’environnement
Conservez dans .env, à côté du fichier Compose, les valeurs qui varient selon le serveur.
mkdir -p ~/arr && cd ~/arrÉcrivez ~/arr/.env :
PUID=1000
PGID=1000
TZ=Etc/UTC
DATA_ROOT=/mnt/dataDéfinissez TZ sur votre propre fuseau, par exemple Europe/Berlin. Les applications arr planifient les tâches et horodatent les lignes de journal dans ce fuseau. Une valeur incorrecte rend ensuite tous les journaux difficiles à comprendre.
Le fichier Compose
Écrivez ~/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-stoppedQuatre éléments de ce fichier jouent un rôle important.
${DATA_ROOT}:/data est identique dans les trois conteneurs qui accèdent aux fichiers multimédias. Prowlarr ne l’utilise pas, car il n’ouvre jamais de fichier multimédia.
Chaque port web est associé à 127.0.0.1. Docker le publie donc uniquement sur l’adresse loopback. Un simple 8989:8989 le publierait sur toutes les interfaces, et les propres règles de pare-feu de Docker feraient passer ce trafic directement au-delà d’une règle deny de ufw. Ce comportement surprend constamment les utilisateurs. Il est expliqué dans pourquoi Docker publie les ports directement au-delà de ufw.
Le port 6881 est volontairement publié sur toutes les interfaces. Il s’agit du port d’écoute du torrent. Il doit être accessible pour les connexions entrantes des pairs. Autorisez-le avec sudo ufw allow 6881. Consultez les bases du pare-feu ufw pour un VPS si cette commande ne vous est pas familière.
Les répertoires de configuration sont séparés pour chaque application, et seul le volume des fichiers multimédias est partagé. Créez-les avant le premier démarrage afin qu’ils appartiennent à votre utilisateur plutôt qu’à root :
mkdir -p ~/arr/config/prowlarr ~/arr/config/sonarr ~/arr/config/radarr ~/arr/config/qbittorrent
docker compose up -d
docker compose psLes quatre services doivent lire running. En juillet 2026, ces images sont publiées sur lscr.io et le tag latest suit la version stable actuelle. Utilisez plutôt un tag de version si vous voulez que les mises à niveau soient décidées à l’avance et non déclenchées par surprise.
Accéder aux interfaces web en toute sécurité
Comme les ports sont liés à loopback, rien n’est encore exposé. Transférez-les via SSH depuis votre propre 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À présent, http://127.0.0.1:8989 dans votre navigateur ouvre Sonarr sur le serveur. Pour un accès permanent, placez la stack derrière Traefik avec des certificats TLS pour plusieurs applications, ou accédez au serveur via un VPN WireGuard que vous hébergez vous-même. Aucune de ces applications ne doit être directement exposée sur Internet avec sa seule page de connexion.
qBittorrent génère un mot de passe administrateur aléatoire lors du premier démarrage et l’inscrit dans le log du conteneur. Consultez-le, puis modifiez-le dans l’interface web :
docker compose logs qbittorrent | grep -i passwordSi vous ne le modifiez pas, un nouveau mot de passe aléatoire est généré à chaque redémarrage et vous devrez consulter les logs à chaque fois.
Définir les chemins dans chaque application
Dans qBittorrent, ouvrez Options, puis Downloads, et définissez le chemin d’enregistrement par défaut sur /data/torrents. Conservez le dossier des téléchargements incomplets dans la même arborescence, par exemple /data/torrents/incomplete. Un téléchargement qui se termine ailleurs que sous /data ne peut pas être lié à la bibliothèque avec un hard link.
Dans Sonarr, ouvrez Settings, puis Media Management, et ajoutez le dossier racine /data/media/Shows. Dans Radarr, le dossier racine est /data/media/Movies. Il s’agit de chemins à l’intérieur du conteneur. Le chemin de l’hôte /mnt/data/media/Shows est rejeté, car ce répertoire n’existe pas du point de vue du conteneur.
Dans Sonarr et Radarr, ouvrez Settings, puis Download Clients, et ajoutez qBittorrent. L’hôte est qbittorrent et le port est 8080. Le nom du service fonctionne comme nom d’hôte, car Compose place les 4 conteneurs sur un même réseau avec un service DNS (domain name system) interne. N’utilisez pas localhost ici : dans le conteneur Sonarr, localhost correspond à Sonarr.
Laissez Remote Path Mappings vide. Cette fonctionnalité sert à traduire un chemin signalé par le client de téléchargement en un chemin visible par l’application arr. Avec un seul montage /data partagé, les 2 conteneurs utilisent déjà les mêmes chemins. C’est la deuxième raison pour laquelle cette configuration est intéressante.
Connecter Prowlarr à Sonarr et Radarr
Prowlarr transmet les définitions des indexers aux autres applications. Vous configurez ainsi un indexer une seule fois au lieu de deux. Prowlarr a besoin d’une clé API (interface de programmation d’application) pour chacune d’elles.
Dans Sonarr, ouvrez Settings, puis General, et copiez la clé API. Dans Prowlarr, ouvrez Settings, puis Apps, ajoutez une application Sonarr et renseignez trois champs. Prowlarr Server est http://prowlarr:9696. Sonarr Server est http://sonarr:8989. API Key correspond à la valeur copiée. Cliquez sur Test. Un résultat vert signifie que Prowlarr a atteint Sonarr via le réseau Compose. Répétez la procédure avec Radarr à l’adresse http://radarr:7878.
Un résultat rouge indiquant que la connexion a été refusée signifie presque toujours que le nom du service est incorrect ou que le préfixe http:// est absent. Vérifiez que le nom est résolu depuis l’intérieur du conteneur :
docker compose exec prowlarr curl -sS -o /dev/null -w '%{http_code}\n' http://sonarr:8989Un code d’état HTTP prouve que le chemin réseau fonctionne. Une erreur de résolution de nom prouve que le nom du service est incorrect.
Vérifier que les hardlinks sont bien créés
Ne faites pas confiance à la configuration tant que vous n’avez pas vérifié le nombre de liens. Après l’importation d’un élément, comparez le fichier téléchargé avec le fichier de la bibliothèque :
stat -c '%i %h %n' /mnt/data/torrents/tv/*/*.mkv
stat -c '%i %h %n' /mnt/data/media/Shows/*/*/*.mkvLe premier nombre est l’inode et le second est le nombre de liens. Un fichier lié par hardlink possède le même inode aux deux emplacements et un nombre de liens égal à 2. Deux inodes différents, chacun avec un nombre de liens égal à 1, signifient que Sonarr a copié le fichier. Le journal d’importation indique alors que la création du hardlink a échoué.
Surveillez également l’espace disque. df -h /mnt/data devrait à peine varier lors d’un import, car un hardlink ajoute un nom, mais aucune donnée.
Ce qui pose réellement problème
Les erreurs d’autorisation lors de l’importation signifient que l’ID utilisateur du conteneur ne peut pas écrire dans le dossier de la bibliothèque. Le message est Access to the path ... is denied. Vérifiez avec ls -ln /mnt/data/media que l’ID du propriétaire correspond à votre PUID, et n’oubliez pas que les répertoires doivent disposer du bit d’exécution avant que le conteneur puisse y accéder.
Les fichiers qui semblent appartenir à root indiquent que le conteneur a démarré avant la création du répertoire sur l’hôte. Docker l’a donc créé en tant que root. Arrêtez la stack, chown le répertoire, puis redémarrez-la.
Si vous supprimez un torrent dans qBittorrent et constatez que le fichier de la bibliothèque a disparu, l’importation était une copie qui a ensuite été supprimée, ou vous avez supprimé les données au lieu de l’entrée du torrent. Avec un véritable hardlink, la suppression d’un nom laisse l’autre intact, car les données ne sont libérées que lorsque le nombre de liens atteint zéro.
Un disque qui se remplit plus vite que la quantité de médias ajoutée indique le problème de copie dans sa forme la plus coûteuse. Exécutez la vérification stat ci-dessus avant d’acheter davantage de stockage.
Ce dont cette stack a besoin sur un VPS
Les trois applications arr sont légères. Elles interrogent les indexers, écrivent dans une petite base de données SQLite et renomment les fichiers. Un serveur avec 2 GB de RAM exécute confortablement les quatre conteneurs. La charge vient d'ailleurs. Un client de téléchargement sature les entrées-sorties du disque avec les torrents volumineux, et un media server qui transcode une vidéo sur le même serveur sollicite fortement le CPU. Stockez les fichiers multimédias sur un volume offrant un débit réel et définissez une limite de bande passante sur le client de téléchargement si le serveur exécute d'autres services importants.
FAQ
Pourquoi Sonarr copie-t-il les fichiers au lieu de créer des liens physiques ?
Parce que la source et la destination se trouvent sur des systèmes de fichiers différents du point de vue du conteneur. Deux bind mounts distincts, comme /downloads et /tv, correspondent à deux systèmes de fichiers, même s’ils proviennent du même disque de l’hôte. Montez un répertoire parent unique sous /data dans chaque conteneur, puis placez-y les téléchargements et la bibliothèque. La création du lien devient alors possible. Vérifiez le résultat avec stat -c '%i %h %n' sur les deux fichiers : ils doivent avoir le même inode et un nombre de liens égal à 2.
Quels PUID et PGID dois-je utiliser ?
Utilisez l’ID numérique du compte de l’hôte qui possède l’arborescence des médias. Vous pouvez l’obtenir avec id -u et id -g. Sur un VPS Ubuntu récemment installé, il s’agit généralement de 1000 pour les deux. Tous les conteneurs de la stack doivent utiliser la même paire. Sinon, une application peut créer des fichiers qu’une autre ne peut pas modifier. Après avoir modifié ces valeurs, recréez les conteneurs avec docker compose up -d --force-recreate, puis corrigez les fichiers existants avec chown -R.
Dois-je exposer ces interfaces web sur Internet ?
Non, et vous ne devriez pas le faire. Liez chaque port publié à 127.0.0.1 dans le fichier Compose, puis accédez aux interfaces par un tunnel SSH, un VPN ou un reverse proxy qui termine TLS (transport layer security) et ajoute sa propre authentification. Les publier directement est plus risqué qu’il n’y paraît, car Docker insère ses propres règles de pare-feu. Une règle deny de ufw n’empêchera donc pas ce trafic.
Où trouver le mot de passe qBittorrent ?
L’image LinuxServer.io affiche un mot de passe temporaire pour l’utilisateur admin dans son journal de démarrage. Exécutez docker compose logs qbittorrent | grep -i password pour le lire, puis définissez un mot de passe permanent dans Options et Web UI. Un nouveau mot de passe temporaire est généré à chaque redémarrage tant que vous n’avez pas défini votre propre mot de passe.
Jellyfin peut-il utiliser les mêmes répertoires ?
Oui, c’est précisément l’objectif de cette organisation. Montez /mnt/data/media dans votre media server sous /media. Ses bibliothèques se trouvent alors dans /media/Movies et /media/Shows, tandis que Sonarr et Radarr écrivent dans ces mêmes répertoires via /data/media. Attribuez au media server les mêmes PUID et PGID afin qu’il puisse lire les fichiers créés par la stack arr.