SSD Nodes Learn Hosting plans →
Guide Matt ConnorDi Matt Connor · Aggiornato 2026-08-28

Stack arr in un unico file Docker Compose

Avvia Prowlarr, Sonarr, Radarr e qBittorrent su un VPS con un solo file Docker Compose, usando PUID, PGID e volumi condivisi per mantenere gli hardlink attivi.

Cosa stai creando

Uno stack arr Docker Compose è composto da quattro container che gestiscono una libreria multimediale: Prowlarr per la configurazione degli indexer, Sonarr per le serie, Radarr per i film e qBittorrent come client di download. I container comunicano tra loro tramite la rete Compose usando il nome del servizio e condividono lo stesso albero di directory sull'host. L'installazione è rapida. Il layout dei volumi determina se lo stack funzionerà per anni o richiederà interventi ogni settimana, quindi gran parte di questa guida è dedicata a questo aspetto.

Lo stack non cerca contenuti al posto tuo. Prowlarr gestisce gli indexer che aggiungi e la scelta degli indexer è una tua decisione e una tua responsabilità legale. Questa guida tratta la configurazione di base: utenti, percorsi, permessi, rete dei container e controlli per verificare che tutto funzioni.

Se non hai mai scritto un file Compose, leggi prima le basi di Docker Compose per un VPS. Questa guida presuppone che docker compose version restituisca già un output sul tuo server.

Quando Sonarr termina un download, importa il file nella libreria. Se la directory 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 aggiuntivo né tempo di copia. Il torrent continua a fare seeding usando il nome originale, mentre il media server legge il nuovo nome.

Se le due directory si trovano su filesystem diversi, il kernel non può creare quel link. Sonarr ricorre quindi a una copia. Una stagione da 40 GB occupa ora 80 GB di spazio e richiede diversi minuti di operazioni di input e output. Inoltre, il log dell'importazione indica che la creazione dell'hardlink è fallita e che il file è stato copiato. Su un VPS con uno spazio disco fisso, è così che si esaurisce lo spazio nel giro di una settimana.

Il problema è questo. All'interno di un container, un bind mount costituisce un confine tra filesystem. Montate /mnt/data/torrents come /downloads e /mnt/data/media come /tv: anche se entrambe le directory risiedono sullo stesso disco dell'host, Sonarr vede due mount separati e rifiuta di creare link tra loro. La documentazione ufficiale dell'immagine LinuxServer.io lo indica chiaramente: usare separatamente i percorsi /downloads e /tv impedisce di creare hardlink.

La soluzione consiste nell'usare un solo mount. Ogni container che accede ai file multimediali riceve lo stesso volume, /mnt/data:/data, e ogni percorso utilizzato è una directory al suo interno. Un punto di mount, un filesystem e hardlink funzionanti.

Crea l'utente, il gruppo e le directory

I container scrivono i file usando un ID utente numerico, impostato da PUID e PGID. Usa il tuo account, così puoi leggere e modificare questi file tramite SSH senza sudo.

id -u
id -g

Entrambi restituiscono in genere 1000 su una VPS Ubuntu appena installata. Ora crea la struttura. Posizionala sul disco che contiene i tuoi file multimediali e mantieni 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/data

Verifica che sia effettivamente un unico filesystem prima di procedere:

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

Entrambe le righe devono mostrare lo stesso dispositivo di origine. Se i dispositivi sono diversi, gli hard link non funzioneranno mai, indipendentemente dalla configurazione del container.

Le directory delle librerie si chiamano intenzionalmente Movies e Shows. Se esegui già Jellyfin come server multimediale, monta /mnt/data/media in Jellyfin come /media: le librerie verranno quindi create in /media/Movies e /media/Shows, esattamente come previsto da quella guida.

Il file dell’ambiente

Mantieni i valori che cambiano da un server all’altro in .env, accanto al file Compose.

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

Scrivi ~/arr/.env:

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

Imposta TZ sulla tua zona, ad esempio Europe/Berlin. Le applicazioni arr pianificano le attività e registrano le righe di log in quella zona, quindi un valore errato rende 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-stopped

Nel file ci sono quattro elementi che svolgono funzioni effettive.

${DATA_ROOT}:/data è identico nei tre container che accedono ai file multimediali. Prowlarr non lo utilizza, perché non apre mai un file multimediale.

Ogni porta web è associata a 127.0.0.1, quindi Docker la pubblica soltanto sull'indirizzo di loopback. Un semplice 8989:8989 la pubblicherebbe su tutte le interfacce e le regole firewall di Docker farebbero passare direttamente il traffico oltre una regola deny di ufw. Questo comportamento sorprende spesso ed è spiegato in perché Docker pubblica le porte bypassando direttamente ufw.

La porta 6881 è pubblicata su tutte le interfacce intenzionalmente. È la porta di ascolto del torrent e deve essere raggiungibile per consentire le connessioni peer in ingresso. Consentila con sudo ufw allow 6881 e consulta le basi del firewall ufw per un VPS se questo comando non ti è familiare.

Le directory di configurazione sono separate per ogni applicazione e solo il volume dei file 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 ps

Tutti 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. Se vuoi che gli aggiornamenti siano una scelta e non una sorpresa, usa invece un tag di versione fisso.

Accedere alle interfacce web in sicurezza

Poiché le porte sono associate all'interfaccia loopback, non è ancora esposto nulla. Inoltrale 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-server

Ora http://127.0.0.1:8989 nel browser consente di raggiungere Sonarr sul server. Per un accesso permanente, pubblica lo stack dietro Traefik con certificati TLS per più applicazioni oppure raggiungi il server tramite una VPN WireGuard gestita autonomamente. Nessuna di queste applicazioni dovrebbe essere esposta su Internet con la sola pagina di login dell'applicazione come protezione. Se scegli il reverse proxy e preferisci usare un solo account per tutte e quattro le interfacce invece di gestire quattro login separati, Authentik fornisce il single sign-on self-hosted che Traefik può applicare a ogni richiesta tramite forward auth.

qBittorrent genera una password casuale per l'amministratore al primo avvio e la stampa nel log del container. Recuperala, quindi modificala nell'interfaccia web:

docker compose logs qbittorrent | grep -i password

Se non la modifichi, a ogni riavvio viene generata una nuova password casuale e dovrai consultare nuovamente i log ogni volta.

Impostare i percorsi all'interno di ogni applicazione

In qBittorrent, apri Options, quindi Downloads, e imposta il percorso predefinito di salvataggio su /data/torrents. Mantieni la cartella dei download incompleti all'interno dello stesso albero, ad esempio /data/torrents/incomplete. Un download completato in un percorso esterno a /data non può essere collegato alla libreria tramite hard link.

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 interni al container. Il percorso 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 hostname perché Compose inserisce tutti e quattro i container nella stessa rete, con un servizio DNS (domain name system) interno. Non usare localhost in questo campo: all'interno del container Sonarr, localhost è Sonarr.

Lascia vuoti i Remote Path Mappings. Questa funzione serve a convertire un percorso riportato dal client di download in un percorso visibile all'applicazione arr. Con un unico mount condiviso /data, entrambi i container utilizzano già gli stessi percorsi. Questo è il secondo motivo per cui questa configurazione è utile.

Collega Prowlarr a Sonarr e Radarr

Prowlarr trasferisce le definizioni degli indexer alle altre applicazioni, quindi configuri un indexer una sola volta invece di due. Per funzionare, richiede una chiave API (application programming interface) da 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 tramite la rete Compose. Ripeti la procedura con Radarr all'indirizzo http://radarr:7878.

Un risultato rosso con l'errore di connessione rifiutata indica quasi sempre un nome del servizio errato o il prefisso http:// mancante. 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:8989

Un codice di stato HTTP dimostra che il percorso di rete funziona. Un errore di risoluzione del nome dimostra che il nome del servizio è errato.

Verifica che i collegamenti fisici vengano effettivamente 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/*/*/*.mkv

Il primo numero è l'inode e il secondo è il numero di collegamenti. Un file collegato tramite 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. Il log dell'importazione segnalerà 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 ma non dati.

Che cosa si rompe realmente

Gli errori di autorizzazione durante l'importazione indicano che l'ID utente del container non può scrivere nella directory 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 devono avere 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 esistesse la directory sull'host, 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 anziché la voce del torrent. Con un hard link 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 contenuti multimediali aggiunti indica il problema delle copie nella sua forma più costosa. Esegui il controllo stat precedente prima di acquistare altro spazio di archiviazione.

Cosa richiede questo stack da un VPS

Le tre applicazioni arr sono leggere. Interrogano gli indexer, 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 arriva da altre attività. Un client di download satura l’I/O del disco durante i torrent di grandi dimensioni, mentre un media server che transcodifica video sullo stesso host utilizza la CPU. Conserva i contenuti multimediali su un volume con un throughput effettivo adeguato e imposta un limite di banda sul client di download se sul server vengono eseguite anche altre attività importanti. Considera separatamente le risorse necessarie per queste attività, invece di presumere che ci sia sempre margine sufficiente: un workspace AFFiNE self-hosted è composto da altri quattro container con un database e, su un server con 2 GB, tende a utilizzare quasi tutta la memoria disponibile. Non tutti i servizi aggiuntivi richiedono altrettante risorse: un servizio con uno scopo unico, come un tracker di allenamento openGym self-hosted, può condividere il server senza problemi, purché gli assegni una configurazione TLS dedicata e sappia dove si trova il file del database prima di affidargli un anno di dati di allenamento. Qualsiasi soluzione che includa un’applicazione web, un database PostgreSQL e una coda di attività gestita da un worker in background richiede risorse più vicine all’estremo rappresentato da AFFiNE. Decidi quindi se un help desk di supporto Chatwoot self-hosted debba essere eseguito su questo server o su un host dedicato, prima di raggiungere il limite durante un’importazione. I carichi di lavoro con picchi improvvisi richiedono ancora più prudenza, perché durante un’importazione conta il picco, non la media: se stai valutando un OneCLI self-hosted che assegna a ogni persona un agent isolato in una sandbox, confronta i requisiti pubblicati con le risorse realmente libere mentre qBittorrent utilizza tutta la banda disponibile, non con il valore mostrato da free -h su un server inattivo.

FAQ

Il motivo è che, dal punto di vista del container, origine e destinazione si trovano su filesystem diversi. Due bind mount separati, come /downloads e /tv, costituiscono due filesystem distinti anche quando provengono dallo stesso disco dell'host. Monta una singola directory padre come /data in ogni container e inserisci al suo interno download e libreria: in questo modo il collegamento diventa possibile. Verifica il risultato con stat -c '%i %h %n' su entrambi i file: devono avere lo stesso inode e un conteggio dei link 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. Su un VPS Ubuntu appena configurato, di solito è 1000 per entrambi. Ogni container dello stack deve usare la stessa coppia. In caso contrario, un'applicazione potrebbe creare 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, quindi accedi alle interfacce tramite un tunnel SSH, una VPN oppure un reverse proxy che termina TLS (Transport Layer Security) e aggiunge una propria autenticazione. Pubblicarle direttamente è più rischioso di quanto sembri, perché Docker inserisce regole firewall proprie e una regola deny di ufw non bloccherà questo 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 in 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 è questo lo scopo della 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.

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