Stack arr in un unico file Docker Compose
Configura Prowlarr, Sonarr, Radarr e qBittorrent su un VPS con PUID, PGID e volumi condivisi: così i collegamenti rigidi continuano a funzionare.
Cosa stai creando
Uno stack arr Docker Compose è composto da quattro container che gestiscono una libreria multimediale: Prowlarr per le impostazioni degli indicizzatori, Sonarr per le serie, Radarr per i film e qBittorrent come client di download. Comunicano tra loro attraverso la rete Compose usando il nome del servizio e condividono un unico albero di directory sull'host. L'installazione è breve. L'elemento che determina se lo stack funzionerà per anni o richiederà interventi ogni settimana è la disposizione dei volumi, quindi gran parte di questa guida è dedicata a questo aspetto.
Lo stack non cerca contenuti autonomamente. Prowlarr contiene gli indicizzatori che aggiungi e la scelta degli indicizzatori spetta a te, così come la relativa responsabilità legale. Questa guida tratta la configurazione tecnica: utenti, percorsi, autorizzazioni, rete dei container e verifiche che confermano il corretto funzionamento.
Se non hai mai scritto un file Compose, leggi prima le nozioni di base su Docker Compose per un VPS. Questa guida presuppone che docker compose version produca già un output sul tuo server.
Perché i collegamenti rigidi si interrompono e perché questo è il punto fondamentale
Quando Sonarr termina un download, importa il file nella libreria. Se la cartella dei download e quella della libreria si trovano sullo stesso filesystem, l'importazione usa un hardlink: un secondo nome che punta agli stessi dati sul disco. Non richiede spazio o tempo aggiuntivi. Il torrent continua il seeding usando il nome precedente, mentre il server multimediale legge quello nuovo.
Se le due cartelle si trovano su filesystem diversi, il kernel non può creare il collegamento. Sonarr esegue quindi una copia. Una stagione da 40 GB occupa ora 80 GB di spazio su disco e richiede diversi minuti di operazioni di input e output; inoltre, il log dell'importazione registra che l'hardlink non è riuscito e che il file è stato copiato. Su un VPS con una quota disco fissa, è così che si esaurisce lo spazio in una settimana.
Ecco il problema. All'interno di un container, un bind mount è un confine del filesystem. Montate /mnt/data/torrents come /downloads e /mnt/data/media come /tv e, anche se entrambi si trovano sullo stesso disco dell'host, Sonarr vede due mount separati e rifiuta di creare collegamenti tra di essi. La documentazione ufficiale dell'immagine LinuxServer.io lo afferma chiaramente: usare i percorsi separati /downloads e /tv impedisce di creare hardlink.
La soluzione consiste nell'usare un solo mount. Ogni container che accede ai contenuti multimediali riceve lo stesso volume, /mnt/data:/data, e ogni percorso utilizzato è una cartella al suo interno. Un punto di mount, un filesystem, hardlink funzionanti.
Creare l'utente, il gruppo e le cartelle
I container scrivono i file utilizzando un ID numerico dell'utente, impostato da PUID e PGID. Usate il vostro account per poter leggere e modificare questi file tramite SSH senza sudo.
id -u
id -gEntrambi restituiscono normalmente 1000 su una VPS Ubuntu appena installata. Ora create la struttura. Posizionatela sul disco che contiene i vostri file multimediali e mantenete l'intera struttura su quel disco.
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/dataVerificate che sia effettivamente un unico filesystem prima di procedere:
df --output=source,target /mnt/data/torrents /mnt/data/mediaEntrambe le righe devono mostrare lo stesso dispositivo sorgente. Se i dispositivi sono diversi, gli hardlink non funzioneranno mai, indipendentemente dalla configurazione del container.
Le cartelle della libreria si chiamano intenzionalmente Movies e Shows. Se eseguite già Jellyfin come server multimediale, montate /mnt/data/media in Jellyfin come /media; le relative librerie saranno quindi disponibili in /media/Movies e /media/Shows, esattamente dove le colloca quella guida.
Il file dell'ambiente
Mantieni i valori che cambiano in base al server in .env, accanto al file Compose.
mkdir -p ~/arr && cd ~/arrScrivi ~/arr/.env:
PUID=1000
PGID=1000
TZ=Etc/UTC
DATA_ROOT=/mnt/dataImposta TZ sul tuo fuso orario, ad esempio Europe/Berlin. Le applicazioni arr pianificano le attività e registrano le righe di log in quel fuso, quindi un valore errato renderà confusi tutti i log in seguito.
Il file Compose
Scrivi ~/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-stoppedIn quel file, quattro elementi svolgono il lavoro effettivo.
${DATA_ROOT}:/data è identico nei tre container che accedono ai contenuti multimediali. Prowlarr non lo riceve, perché non apre mai un file multimediale.
Ogni porta web è associata a 127.0.0.1, quindi Docker la pubblica solo sull'indirizzo di loopback. Un semplice 8989:8989 la pubblicherebbe su ogni interfaccia, e le regole firewall di Docker farebbero passare il traffico direttamente oltre una regola deny di ufw. Questo comportamento sorprende spesso ed è spiegato in perché Docker pubblica le porte oltre ufw.
La porta 6881 è pubblicata su tutte le interfacce intenzionalmente. È la porta di ascolto del torrent e deve essere raggiungibile per le connessioni peer in ingresso. Consentila con sudo ufw allow 6881 e consulta le nozioni di base sul firewall ufw per un VPS se il comando non ti è familiare.
Le directory di configurazione sono separate per ogni applicazione e solo il volume dei contenuti multimediali è condiviso. Creale prima del primo avvio, in modo che appartengano al tuo utente anziché a root:
mkdir -p ~/arr/config/prowlarr ~/arr/config/sonarr ~/arr/config/radarr ~/arr/config/qbittorrent
docker compose up -d
docker compose psTutti e quattro i servizi devono leggere running. A luglio 2026 queste immagini sono pubblicate su lscr.io e il tag latest segue la release stabile corrente. Usa invece un tag di versione se vuoi che gli aggiornamenti siano una scelta consapevole anziché una sorpresa.
Accedere in sicurezza alle interfacce web
Poiché le porte sono associate all'interfaccia loopback, non è ancora esposto nulla. Esegui il forwarding delle porte tramite SSH dal tuo computer:
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-serverOra http://127.0.0.1:8989 nel browser apre Sonarr sul server. Per un accesso permanente, posiziona lo stack dietro Traefik con certificati TLS per più applicazioni oppure accedi al server tramite una VPN WireGuard gestita autonomamente. Nessuna di queste applicazioni dovrebbe essere esposta direttamente su Internet con la sola pagina di accesso integrata come protezione.
qBittorrent genera una password amministratore casuale al primo avvio e la stampa nel log del container. Leggila, quindi modificala nell'interfaccia web:
docker compose logs qbittorrent | grep -i passwordSe ometti questa modifica, a ogni riavvio viene generata una nuova password casuale e dovrai consultare nuovamente i log.
Impostare i percorsi all'interno di ogni applicazione
In qBittorrent, apri Options, quindi Downloads, e imposta il percorso di salvataggio predefinito su /data/torrents. Mantieni la cartella dei download incompleti all'interno dello stesso albero, ad esempio /data/torrents/incomplete. Un download che termina in qualsiasi percorso esterno a /data non può essere collegato tramite hard link alla libreria.
In Sonarr, apri Settings, quindi Media Management, e aggiungi la cartella radice /data/media/Shows. In Radarr, la cartella radice è /data/media/Movies. Questi sono percorsi all'interno del container. Il percorso dell'host /mnt/data/media/Shows viene rifiutato perché quella directory non esiste dal punto di vista del container.
In Sonarr e Radarr, apri Settings, quindi Download Clients, e aggiungi qBittorrent. L'host è qbittorrent e la porta è 8080. Il nome del servizio funziona come nome host perché Compose inserisce tutti e quattro i container nella stessa rete, con un servizio DNS interno (domain name system). Non usare localhost in questo campo: all'interno del container Sonarr, localhost è Sonarr.
Lascia vuota la sezione Remote Path Mappings. Questa funzione serve a convertire un percorso segnalato dal client di download in un percorso visibile all'applicazione arr. Con un unico mount condiviso /data, entrambi i container usano già gli stessi percorsi. Questo è il secondo motivo per cui questa configurazione è utile.
Collegare Prowlarr a Sonarr e Radarr
Prowlarr trasferisce le definizioni degli indicizzatori alle altre applicazioni. In questo modo configuri un indicizzatore una sola volta invece di due. Prowlarr richiede una chiave API (interfaccia di programmazione dell'applicazione) per ciascuna applicazione.
In Sonarr, apri Settings, quindi General, e copia la chiave API. In Prowlarr, apri Settings, quindi Apps, aggiungi un'applicazione Sonarr e compila tre campi. Prowlarr Server è http://prowlarr:9696. Sonarr Server è http://sonarr:8989. API Key è il valore copiato. Premi Test. Un risultato verde indica che Prowlarr ha raggiunto Sonarr attraverso la rete Compose. Ripeti la procedura con Radarr all'indirizzo http://radarr:7878.
Un risultato rosso con il messaggio di connessione rifiutata indica quasi sempre un nome di servizio errato o l'assenza del prefisso http://. Verifica che il nome venga risolto dall'interno del container:
docker compose exec prowlarr curl -sS -o /dev/null -w '%{http_code}\n' http://sonarr:8989Un codice di stato HTTP dimostra che il percorso di rete funziona. Un errore di risoluzione del nome dimostra che il nome del servizio è errato.
Verificare che i collegamenti fisici vengano realmente creati
Non considerare completata la configurazione finché non hai verificato il numero di collegamenti. Dopo l'importazione di un elemento, confronta il file scaricato con il file nella libreria:
stat -c '%i %h %n' /mnt/data/torrents/tv/*/*.mkv
stat -c '%i %h %n' /mnt/data/media/Shows/*/*/*.mkvIl primo numero è l'inode e il secondo è il numero di collegamenti. Un file collegato con un hardlink mostra lo stesso inode in entrambe le posizioni e un numero di collegamenti pari a 2. Due inode diversi, ciascuno con un numero di collegamenti pari a 1, indicano che Sonarr ha copiato il file; nel registro di importazione verrà indicato che la creazione dell'hardlink non è riuscita.
Controlla anche lo spazio su disco. df -h /mnt/data dovrebbe cambiare appena durante un'importazione, perché un hardlink aggiunge un nome, non dati.
Cosa si rompe realmente
Gli errori di autorizzazione durante l'importazione indicano che l'ID utente del container non può scrivere nella cartella della libreria. Il messaggio è Access to the path ... is denied. Verifica con ls -ln /mnt/data/media che l'ID del proprietario corrisponda al tuo PUID e ricorda che le directory richiedono il bit di esecuzione prima che il container possa accedervi.
I file che risultano appartenere a root indicano che il container è stato avviato prima che la directory dell'host esistesse, quindi Docker l'ha creata come root. Arresta lo stack, chown la directory e riavvialo.
Se elimini un torrent da qBittorrent e il file della libreria scompare, significa che l'importazione era una copia poi rimossa oppure che hai eliminato i dati invece della voce del torrent. Con un hardlink reale, la rimozione di un nome lascia intatto l'altro, perché i dati vengono liberati solo quando il conteggio dei link raggiunge zero.
Un disco che si riempie più rapidamente dei file multimediali aggiunti indica il problema delle copie nella sua forma più costosa. Esegui il controllo stat precedente prima di acquistare altro spazio di archiviazione.
Requisiti di questo stack per un VPS
Le tre applicazioni arr richiedono poche risorse. Interrogano periodicamente gli indicizzatori, scrivono in un piccolo database SQLite e rinominano i file. Un server con 2 GB di RAM esegue comodamente tutti e quattro i container. Il carico proviene da altre attività. Un client di download satura le operazioni di input e output del disco durante i torrent di grandi dimensioni, mentre un media server che transcodifica video sullo stesso server utilizza la CPU. Conservare i file multimediali su un volume con un throughput effettivo e impostare un limite di banda sul client di download se sul server sono in esecuzione altre attività importanti.
FAQ
Perché Sonarr copia i file invece di crearne hard link?
Perché, dal punto di vista del container, origine e destinazione si trovano su filesystem diversi. Due bind mount separati, come /downloads e /tv, sono due filesystem anche quando provengono dallo stesso disco dell'host. Monta una singola directory padre come /data in ogni container e inserisci al suo interno i download e la libreria. In questo modo il collegamento diventa possibile. Conferma il risultato con stat -c '%i %h %n' su entrambi i file: lo stesso inode e un conteggio dei collegamenti pari a 2.
Quali PUID e PGID devo usare?
Usa l'id numerico dell'account dell'host proprietario dell'albero multimediale. Puoi ricavarlo con id -u e id -g. In un VPS Ubuntu appena installato, di solito è 1000 per entrambi. Tutti i container dello stack devono usare la stessa coppia. In caso contrario, un'applicazione crea file che un'altra non può modificare. Dopo aver cambiato i valori, ricrea i container con docker compose up -d --force-recreate e correggi i file esistenti con chown -R.
Devo esporre queste interfacce web su Internet?
No, e non dovresti farlo. Associa ogni porta pubblicata a 127.0.0.1 nel file Compose. Puoi quindi accedere alle interfacce tramite un tunnel SSH, una VPN o un reverse proxy che termina TLS (sicurezza del livello di trasporto) e aggiunge una propria autenticazione. Pubblicarle direttamente è più rischioso di quanto sembri, perché Docker inserisce proprie regole firewall e una regola ufw deny non bloccherà quel traffico.
Dove trovo la password di qBittorrent?
L'immagine LinuxServer.io stampa una password temporanea per l'utente admin nel log di avvio. Esegui docker compose logs qbittorrent | grep -i password per leggerla, quindi imposta una password permanente nelle sezioni Options e Web UI. A ogni riavvio viene generata una nuova password temporanea finché non ne imposti una personalizzata.
Jellyfin può usare le stesse directory?
Sì, ed è proprio lo scopo di questa struttura. Monta /mnt/data/media nel media server come /media. Le relative librerie si trovano in /media/Movies e /media/Shows, mentre Sonarr e Radarr scrivono nelle stesse directory tramite /data/media. Assegna al media server gli stessi PUID e PGID, così potrà leggere ciò che scrive lo stack arr.