SSD Nodes Learn 🎉 VPS da $5.50/mese
Guide Matt ConnorDi Matt Connor · Aggiornato 2026-08-13

PUID e PGID in Docker Compose: cosa fanno davvero

PUID e PGID non sono impostazioni Docker: sono una convenzione degli entrypoint linuxserver.io. Scopri perché i file finiscono come 911:911 e come correggerli.

Cosa sono realmente PUID e PGID

PUID e PGID sono due variabili d'ambiente che alcune immagini container leggono all'avvio. Docker non le considera. Sono una convenzione utilizzata dalle immagini linuxserver.io e da poche altre immagini. Un'immagine che non è stata progettata per leggerle le ignora senza segnalazioni.

All'interno di un'immagine linuxserver.io esiste un utente chiamato abc, creato durante la build con UID (ID utente) 911 e GID (ID gruppo) 911. Il container viene avviato come root ed esegue i propri script di inizializzazione. Uno di questi script modifica l'ID dell'utente prima di qualsiasi altra operazione:

groupmod -o -g "${PGID}" abc
usermod -o -u "${PUID}" abc

Il flag -o consente di usare un ID già assegnato altrove. Al termine, lo script rimuove i privilegi e avvia l'applicazione come abc. Pertanto PUID=1000 non arriva mai a Docker. La variabile modifica l'ID di un utente all'interno del container prima dell'avvio dell'applicazione. Di conseguenza, ogni file scritto dall'applicazione sul disco viene assegnato a 1000. Se non imposti PUID, abc mantiene il valore 911. Per questo un bind mount non configurato si riempie di file di proprietà di 911:911.

Recuperare i due numeri con id

Eseguire questo comando sull'host, con l'utente proprietario delle directory dei dati:

id
uid=1000(deploy) gid=1000(deploy) groups=1000(deploy),27(sudo),988(docker)

uid è il PUID e gid è il PGID. In uno script, id -u e id -g stampano soltanto i numeri. Nella maggior parte delle immagini VPS appena installate, il primo account personale usa 1000:1000, ma non bisogna darlo per scontato. Un server ricreato o un secondo account aggiunto in seguito può usare 1001 o un valore superiore. Un numero errato in questo punto è l'intero problema. Se i servizi vengono eseguiti con un account di servizio dedicato invece del proprio utente di accesso, eseguire id thatuser e usare i numeri restituiti.

Perché i file risultano di proprietà di 911:911

ls -l stampa un ID numerico invece di un nome quando nessun account dell’host corrisponde a quell’ID. Sul server non esiste alcun account con UID 911, quindi non c’è un nome da stampare. Usa ls -ln per visualizzare sempre i numeri ed eliminare l’ambiguità:

ls -ln /srv/appdata/sonarr
drwxr-xr-x 2 911 911 4096 Aug  7 09:12 Backups
-rw-r--r-- 1 911 911  512 Aug  7 09:12 config.xml

L’output indica che il container è stato eseguito con i valori predefiniti incorporati. Verificalo dall’interno del container invece di procedere per supposizioni:

docker exec sonarr id abc
docker compose logs sonarr | head -n 25

L’inizializzazione di linuxserver stampa il risultato nel log di avvio su due righe:

User UID:    911
User GID:    911

Se dopo aver impostato PUID=1000 nel file Compose quelle righe riportano 911, la variabile non è mai arrivata al container. La causa più comune è aver modificato docker-compose.yml e poi eseguito docker compose restart, che riutilizza il container esistente con il relativo ambiente originale. Le modifiche all’ambiente richiedono docker compose up -d, che ricrea il container.

Perché non puoi eliminare un file scritto dal container

Il kernel confronta numeri, non nomi. La shell viene eseguita con UID 1000. Il file appartiene a UID 911. La directory che lo contiene è drwxr-xr-x e appartiene anch'essa a 911, quindi il gruppo e gli altri utenti dispongono dei permessi di lettura ed esecuzione, ma non di scrittura. Per eliminare un file è necessario il permesso di scrittura sulla sua directory, non sul file. Per questo ottieni il seguente risultato anche quando il file sembra innocuo:

rm: cannot remove '/srv/appdata/sonarr/config.xml': Permission denied

Un container che deve scrivere incontra lo stesso problema dal lato opposto. Se la directory dell'host appartiene al tuo utente ed è impostata a 755, mentre l'applicazione viene eseguita con UID 911, la prima operazione di scrittura fallisce con Permission denied e l'applicazione lo segnala con il proprio messaggio. In un'applicazione .NET come Sonarr o Radarr, l'errore viene mostrato come UnauthorizedAccessException: Access to the path '/data/downloads' is denied. La stringa dei permessi davanti al file indica a quale dei tre insiemi di permessi sei effettivamente soggetto. Interpretare correttamente drwxr-xr-x è ciò che trasforma questo errore da misterioso a ovvio.

Questo è nello specifico un problema di bind mount. Quando Docker crea un named volume vuoto e lo monta su un percorso già presente nell'immagine, copia nel volume il contenuto di quel percorso, inclusi proprietario e bit dei permessi. L'applicazione trova quindi una directory di cui è già proprietaria. Un bind mount non riceve questo trattamento: Docker monta la directory dell'host esattamente nello stato in cui si trova. Questa differenza è uno dei motivi pratici per cui è importante sapere quando un bind mount è preferibile a un named volume e quando non lo è.

Correggere una directory con proprietario o gruppo errati

L'impostazione di PUID e PGID modifica il comportamento dell'applicazione da quel momento in poi. Non corregge retroattivamente i file già presenti sul disco. Arresta lo stack, correggi manualmente il proprietario e il gruppo, quindi avvialo di nuovo:

docker compose down
sudo chown -R 1000:1000 /srv/appdata/sonarr
docker compose up -d

Usa sudo chown -R "$(id -u):$(id -g)" /srv/appdata/sonarr se preferisci non digitare i numeri. Esegui questa operazione con il container arrestato, perché un'applicazione in esecuzione che sta scrivendo durante un chown ricorsivo può lasciare una struttura di directory corretta solo in parte e causare una seconda serie di errori difficili da interpretare.

Cosa non risolvono PUID e PGID

Questa è la parte che causa problemi anche a chi ha configurato tutto correttamente. All'avvio, l'init di linuxserver esegue chown esattamente su tre percorsi: /app, /config e /defaults. I mount dei file multimediali non sono inclusi in questo elenco. /data, /downloads e /tv vengono passati all'applicazione senza modifiche. Se il lato host di questi mount ha una proprietà che impedisce la scrittura all'utente del container, il container si avvia correttamente, mostra nel banner l'UID corretto e poi fallisce al primo import.

Questo comportamento è corretto. Eseguire un chown ricorsivo su una libreria multimediale di dodici terabyte a ogni avvio del container sarebbe un problema grave. Di conseguenza, la gestione delle directory dei file multimediali spetta a voi. Sono questi i mount in cui si verificano realmente i problemi di autorizzazioni.

Tre modalità per controllare l’utente e quando usare ciascuna

Variabili d’ambiente PUID e PGID

Funziona solo con le immagini il cui entrypoint legge queste variabili. È un approccio diffuso perché il container viene comunque avviato come root, completa la propria inizializzazione, corregge /config e solo dopo riduce i privilegi. Docker Mods e gli script di inizializzazione personalizzati continuano a funzionare. Il compromesso è affidarsi a una convenzione anziché a una funzionalità della piattaforma; inoltre, i nomi delle variabili non sono standard tra i diversi progetti.

La chiave user: in Compose

Questa è una funzionalità reale di Docker e funziona con qualsiasi immagine, perché il runtime del container la applica prima dell’esecuzione del codice dell’immagine:

services:
  sonarr:
    image: lscr.io/linuxserver/sonarr:latest
    user: "1000:1000"

Il processo non viene mai eseguito come root, nemmeno per un istante. Questo costituisce un effettivo vantaggio di sicurezza. Tuttavia, impedisce il funzionamento di qualsiasi componente dell’entrypoint che richieda i privilegi root. Nelle immagini linuxserver, il progetto supporta questa modalità in base a ragionevoli possibilità e solo per le immagini che ha testato. Le limitazioni sono specifiche: PUID e PGID non hanno più alcun effetto, Docker Mods non viene eseguito, i servizi personalizzati non vengono avviati e la gestione dei permessi su ogni volume montato ricade su di te. Il modello documentato abbina il flag a un /run scrivibile:

user: 1000:1000
tmpfs:
  - /run:uid=1000,gid=1000,exec
security_opt:
  - no-new-privileges=true

Un effetto solo estetico può sorprendere. Un user: numerico non ha una voce corrispondente nel /etc/passwd del container, quindi gli strumenti al suo interno mostrano whoami: cannot find name for user ID 1000. L’ID è valido e l’accesso ai file funziona normalmente. Fallisce soltanto la risoluzione del nome.

Docker rootless

Docker rootless esegue il daemon con il tuo utente senza privilegi, quindi nessun componente del sistema viene eseguito come root reale. Questo cambia completamente il calcolo della proprietà. L’UID 0 del container viene mappato sull’UID dell’utente che esegue Docker rootless, mentre l’UID n del container per qualsiasi n pari o superiore a 1 viene mappato su subuid + (n - 1), dove subuid è la base dell’intervallo assegnato all’utente in /etc/subuid e /etc/subgid. Docker richiede che siano disponibili almeno 65,536 ID subordinati.

Rileggi questa mappatura, perché inverte le indicazioni abituali. Con Docker rootless, un container che scrive come root crea file di tua proprietà. Un container che scrive con UID 1000 crea file di proprietà di un ID subordinato vicino a 100999, che la tua shell non può modificare. Di conseguenza, il valore PUID corretto con un daemon rootful è errato in questo contesto. I due meccanismi risolvono lo stesso problema a livelli diversi; combinarli senza verificare la mappatura è il modo per ritrovarsi con una directory che devi sudo per poterla eliminare. Se scegli la modalità rootless, verifica la proprietà di un file creato sul tuo server prima di migrarvi una libreria.

Per la maggior parte degli stack self-hosted su un singolo VPS, usare PUID e PGID con un daemon rootful è la scelta più pratica, perché è il modello per cui le immagini sono state sviluppate e documentate. Usa user: quando il README dell’immagine indica che è stato testato per questa modalità, oppure quando esegui un’immagine ufficiale upstream che non supporta affatto PUID. Un workspace per documenti come un’istanza self-hosted di AFFiNE su un singolo VPS rientra nell’ultimo caso, perché nessuno dei suoi container legge PUID e la proprietà della directory del database e dei file caricati viene determinata dal runtime, non da alcun elemento del blocco environment.

Il caso dello stack multimediale: un gruppo condiviso tra i container

Uno stack multimediale arr con Sonarr, Radarr e un client di download è il caso in cui questo problema diventa concreto. Il client di download scrive il file completato in /data/downloads. Sonarr crea quindi un hard link oppure sposta il file in /data/media. Per creare l'hard link, i due container devono avere accesso in scrittura allo stesso albero di directory. Se il client di download viene eseguito come 1000 mentre Sonarr viene eseguito come 1001, uno dei due diventa proprietario dei file e l'altro può soltanto leggerli.

La soluzione consiste nell'usare un gruppo condiviso che tutti i container dello stack impostano come PGID:

sudo groupadd -g 13000 media
sudo usermod -aG media deploy
sudo chown -R deploy:media /srv/media
sudo find /srv/media -type d -exec chmod 2775 {} +
sudo find /srv/media -type f -exec chmod 0664 {} +

Il 2 iniziale in 2775 è il bit setgid. Applicato a una directory, indica che ogni nuovo file e sottodirectory creati al suo interno ereditano il gruppo media invece del gruppo primario dell'utente che li crea. In questo modo la configurazione continua a funzionare anche per i nuovi download, senza dover eseguire nuovamente chown. Eseguire il logout e accedere nuovamente, oppure eseguire newgrp media, prima di verificare il proprio accesso: un gruppo aggiunto con usermod -aG non compare in una sessione shell già aperta.

All'interno del container, groupmod -o -g 13000 abc riassegna al gruppo abc il GID 13000. In questo modo abc scrive usando lo stesso GID del gruppo media sull'host. Ogni container dello stack mantiene il proprio PUID e condivide lo stesso PGID.

Impostare quindi UMASK=002 su ogni container linuxserver dello stack. Questo è il passaggio che spesso viene omesso. Il valore predefinito di queste immagini è UMASK=022, che rimuove il bit di scrittura del gruppo da ogni nuovo file. I file vengono quindi creati come 0644 e la condivisione appena configurata non produce alcun effetto. 002 crea file 0664 e directory 0775, consentendo al gruppo di scrivere:

services:
  sonarr:
    image: lscr.io/linuxserver/sonarr:latest
    container_name: sonarr
    environment:
      - PUID=${PUID}
      - PGID=${PGID}
      - UMASK=002
      - TZ=Etc/UTC
    volumes:
      - /srv/appdata/sonarr:/config
      - /srv/media:/data
    restart: unless-stopped

Inserire questi due valori in un file .env accanto al file Compose, così l'intero stack usa un'unica definizione:

PUID=1000
PGID=13000

Compose legge automaticamente quel file per la sostituzione in stile ${PUID}, tramite lo stesso meccanismo usato per le credenziali. Le regole per mantenere i valori fuori da docker-compose.yml e inserirli in un file .env si applicano anche in questo caso, con una differenza: questi due numeri non sono segreti.

Verificare il funzionamento end-to-end invece di affidarsi alla configurazione. Scrivere un file dall'interno di un container e leggerlo dall'host:

docker exec sonarr touch /data/downloads/permtest
ls -ln /srv/media/downloads/permtest

Un risultato corretto mostra il proprio PUID come proprietario, 13000 come gruppo e -rw-rw-r-- come modalità. Se il gruppo è 1000, nella directory manca il bit setgid. Se la modalità è -rw-r--r--, la variabile UMASK non è stata applicata. Verificare quindi di aver ricreato il container invece di averlo soltanto riavviato. Al termine, rimuovere il file di test con rm /srv/media/downloads/permtest.

Quale immagine usa quale variabile

Le immagini di linuxserver.io usano PUID, PGID e UMASK. Paperless-ngx usa nomi diversi per lo stesso concetto: USERMAP_UID e USERMAP_GID, entrambi con valore predefinito 1000; la relativa documentazione indica di ricavarli da id -u e id -g. Anche i server fotografici presentano differenze analoghe: PhotoPrism usa la propria coppia PHOTOPRISM_UID e PHOTOPRISM_GID, mentre Immich non fornisce un equivalente e lascia che l'utente del container sia determinato dalla chiave user: di Docker. Di conseguenza, scegliere tra PhotoPrism e Immich determina anche quale di questi meccanismi dovrai mantenere per la libreria più grande del server. Molte immagini ufficiali upstream, incluse le comuni immagini per database e web server, usano un utente integrato fisso e prevedono che tu utilizzi user: oppure che lo lasci invariato. Lo stesso vale per l'infrastruttura che aggiungerai in seguito: mettere Authentik davanti alle applicazioni per usare un unico login significa eseguire le immagini ufficiali di server, Postgres e Redis, che non leggono alcun PUID. La proprietà dei volumi viene quindi determinata dal runtime, non da un entrypoint configurabile.

Controlla quindi il README di ogni immagine prima di copiare un blocco di variabili d'ambiente da un progetto all'altro. Docker passa al container qualsiasi variabile d'ambiente impostata, indipendentemente dal fatto che un componente interno la utilizzi. Un PUID non utilizzato da alcun componente non produce errori, avvisi né effetti. Il container viene eseguito con l'utente specificato dall'ultima configurazione del relativo Dockerfile. Lo puoi verificare osservando la proprietà dei file che il container scrive.

FAQ

Perché i miei file Docker appartengono a 911:911?

911 è l'UID e il GID dell'utente abc integrato nelle immagini linuxserver.io. Se viene visualizzato, significa che il container è stato avviato senza impostare PUID e PGID, quindi lo script di inizializzazione ha lasciato i valori predefiniti integrati. ls -l mostra i numeri grezzi perché sul sistema host non esiste alcun account con ID 911; di conseguenza non c'è un nome da visualizzare. Imposta PUID e PGID sull'output di id, ricrea il container con docker compose up -d, quindi correggi i file esistenti con sudo chown -R 1000:1000 nella directory interessata.

PUID e PGID funzionano con tutte le immagini Docker?

No. Non sono funzionalità di Docker e Docker non li legge mai. Funzionano soltanto con le immagini il cui entrypoint li legge ed esegue usermod e groupmod prima di avviare l'applicazione. Questo vale per la famiglia linuxserver.io e per alcuni progetti che ne hanno copiato il modello. Altri progetti usano nomi diversi, ad esempio USERMAP_UID e USERMAP_GID in paperless-ngx. Se un'immagine non legge nessuna di queste variabili, le accetta e le ignora senza alcun avviso.

Devo usare PUID e PGID oppure la chiave user: in Docker Compose?

Usa PUID e PGID quando l'immagine li supporta, perché l'entrypoint continua a essere eseguito come root abbastanza a lungo da correggere /config e avviare correttamente i propri servizi. Usa user: quando l'immagine non supporta PUID oppure quando il README dell'immagine dichiara che il funzionamento senza root è stato verificato. In un'immagine linuxserver, impostare user: rende inattivi PUID e PGID, impedisce l'esecuzione di Docker Mods e dei servizi personalizzati e rende tua la responsabilità dei permessi di ogni volume montato.

Sonarr ha il PUID corretto, ma non riesce comunque a spostare i file. Qual è il problema?

Controlla tre aspetti, nell'ordine. Primo, il mount dei file multimediali: lo script di inizializzazione esegue chown soltanto su /app, /config e /defaults, quindi /data o /downloads conserva la proprietà che ha sul sistema host. Secondo, il gruppo condiviso: se il client di download e Sonarr vengono eseguiti con GID diversi, nessuno dei due può modificare i file dell'altro; assegna quindi lo stesso PGID a ogni container dello stack. Terzo, la umask: il valore predefinito dell'immagine, UMASK=022, crea i file come 0644 senza il bit di scrittura per il gruppo, rendendo di fatto inutilizzabile un gruppo condiviso. Imposta UMASK=002 e attiva il bit setgid sulle directory con chmod 2775, così i nuovi file ereditano il gruppo.