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.
Che cosa sono realmente PUID e PGID
PUID e PGID sono due variabili d'ambiente che determinate immagini di container leggono all'avvio. Docker non le legge direttamente. Sono una convenzione utilizzata dalle immagini linuxserver.io e da alcune 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 compilazione con UID (ID utente) 911 e GID (ID gruppo) 911. Il container viene avviato come root ed esegue gli script di inizializzazione. Uno di questi script riassegna l'ID di quell'utente prima di qualsiasi altra operazione:
groupmod -o -g "${PGID}" abc
usermod -o -u "${PUID}" abcIl flag -o consente di utilizzare un ID già in uso altrove. Al termine, lo script di inizializzazione riduce i privilegi e avvia l'applicazione come abc. Docker passa quindi PUID=1000 al container come una normale variabile d'ambiente e non esegue alcuna operazione su di essa. La variabile riassegna l'ID di un utente all'interno del container prima dell'avvio dell'applicazione. Di conseguenza, con un daemon rootful standard, ogni file scritto dall'applicazione sul disco risulta di proprietà dell'utente con ID 1000. Docker rootless modifica questo numero, come spiegato più avanti. Se PUID non è impostata, abc conserva l'ID 911. Per questo un bind mount non configurato si riempie di file appartenenti a 911:911.
Recuperare i due numeri con id
Eseguire questo comando sull'host, con l'utente proprietario delle directory dei dati:
iduid=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 sull'host corrisponde a quell'ID. Sul server non esiste alcun utente con UID 911, quindi non c'è alcun nome da stampare. Usa ls -ln per visualizzare sempre i numeri ed eliminare l'ambiguità:
ls -ln /srv/appdata/sonarrdrwxr-xr-x 2 911 911 4096 Aug 7 09:12 Backups
-rw-r--r-- 1 911 911 512 Aug 7 09:12 config.xmlL'output indica che il container è stato eseguito con i valori predefiniti integrati. config.xml nell'elenco è anche il file che contiene le impostazioni di autenticazione. Questo è importante la prima volta che apri l'interfaccia web e scopri che Sonarr e Radarr vengono distribuiti senza nome utente o password predefiniti. Verificalo dall'interno del container invece di procedere per tentativi:
docker exec sonarr id abc
docker compose logs sonarr | head -n 25L'inizializzazione di linuxserver stampa il risultato nel log di avvio su due righe:
User UID: 911
User GID: 911Se dopo aver impostato PUID=1000 nel file Compose queste righe riportano 911, il container in esecuzione non sta usando il valore impostato. La causa più comune è che hai modificato docker-compose.yml e poi eseguito docker compose restart, che riutilizza il container esistente con il suo ambiente originale. Le modifiche all'ambiente richiedono docker compose up -d, che ricrea il container. L'altra causa è intenzionale: quando un'immagine viene eseguita in modalità di sola lettura o non-root, l'inizializzazione salta completamente la riassegnazione degli ID e stampa l'ID invariato di abc, anche se la variabile è presente nel 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 deniedUn 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 -dUsa 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 riguarda chi ha configurato tutto correttamente. All'avvio, l'init di linuxserver esegue chown esattamente su tre percorsi: /app, /config e /defaults. I mount dei media non sono inclusi nell'elenco. /data, /downloads e /tv vengono passati all'applicazione senza modifiche. Se il lato host di questi mount ha una proprietà che impedisce all'utente del container di scrivere, il container si avvia correttamente, visualizza l'UID corretto nel banner e poi fallisce al primo import.
Questo comportamento è corretto. Eseguire un chown ricorsivo chown su una libreria multimediale da dodici terabyte a ogni avvio del container sarebbe un problema serio. Significa però che la gestione delle directory dei media spetta a voi. Sono proprio questi i mount in cui si verificano i problemi di autorizzazioni. Poiché questo tipo di errore compare in modo silenzioso nel log dell'applicazione, anche ore dopo che il container sembrava funzionare correttamente, un test periodico di scrittura integrato con ntfy sul proprio VPS, con invio degli avvisi al telefono è un modo semplice per rilevarlo prima che una settimana di episodi mancanti renda evidente il problema.
Tre modi per controllare l'utente e quando usare ciascuno
Variabili d'ambiente PUID e PGID
Funziona soltanto con le immagini il cui entrypoint le legge. È un approccio diffuso perché il container viene comunque avviato come root, esegue la propria configurazione, corregge /config e solo dopo abbandona i privilegi. Docker Mods e gli script init 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 vari progetti.
La chiave user: in Compose
Questa è una funzionalità Docker effettiva 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, neppure per un istante, con un autentico vantaggio per la sicurezza. Tuttavia, questo interrompe qualsiasi operazione dell'entrypoint che richieda root. Nelle immagini linuxserver il progetto supporta questa modalità con ragionevole impegno e soltanto 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 eseguiti e la gestione dei permessi su ogni volume montato ricade sull'utente. Il modello documentato associa il flag a un /run scrivibile:
user: 1000:1000
tmpfs:
- /run:uid=1000,gid=1000,exec
security_opt:
- no-new-privileges=trueUn effetto puramente estetico può sorprendere. Un user: numerico che non ha una voce corrispondente nel /etc/passwd del container fa sì che gli strumenti al suo interno mostrino whoami: cannot find name for user ID 1000. L'ID è valido e l'accesso ai file funziona normalmente. Fallisce soltanto la ricerca del nome.
Docker rootless
Docker rootless esegue il daemon stesso con l'utente non privilegiato, quindi nessun componente del sistema viene eseguito come root reale. Questo cambia completamente il calcolo della proprietà. L'UID 0 del container viene associato all'UID dell'utente host che esegue Docker rootless, mentre l'UID n del container, per qualsiasi n pari o superiore a 1, viene associato a 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 associazione, perché inverte la raccomandazione usuale. Con Docker rootless, un container che scrive come root crea file di proprietà dell'utente. Un container che scrive come UID 1000 crea file di proprietà di un ID subordinato intorno a 100999, che la shell dell'utente 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 e combinarli senza verificare la configurazione è il modo più semplice per ritrovarsi con una directory che richiede sudo per essere rimossa. 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, PUID e PGID con un daemon rootful sono la scelta pragmatica, perché è la modalità per cui le immagini sono state realizzate e documentate. Usa user: quando il README dell'immagine dichiara che è stata testata con questa modalità oppure quando esegui un'immagine ufficiale upstream che non supporta affatto PUID. Un'area di lavoro per documenti come un'istanza AFFiNE self-hosted 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 qualcosa nel blocco environment. Lo stesso vale per un help desk Chatwoot self-hosted, in cui il container Rails e il worker Sidekiq scrivono entrambi in una directory per gli upload e nessuno dei due legge PUID; di conseguenza, la directory deve corrispondere all'utente con cui l'immagine viene già eseguita. Nulla cambia con uno stack più recente: assegnare a ogni membro del team il proprio agente OneCLI isolato lascia le directory di lavoro individuali e la directory dei dati Postgres di proprietà dell'utente con cui viene già eseguita ciascuna immagine, trasformando il problema in una questione di user: e chown anziché di PUID. Se metti una singola API self-hosted davanti a Codex, Claude Code e Hermes, erediti la stessa configurazione, perché quell'immagine viene eseguita con il proprio utente integrato e il bind mount che contiene il database e le chiavi memorizzate assume la proprietà associata a quell'utente.
Il caso dello stack multimediale: un gruppo condiviso tra i container
Un media stack 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 hardlink o sposta il file in /data/media. Per creare l'hardlink, 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 in un gruppo condiviso che tutti i container dello stack usano 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 ogni nuova sottodirectory creati al suo interno ereditano il gruppo media invece del gruppo primario del processo che li crea. In questo modo la configurazione resta valida anche per i nuovi download e non è necessario eseguire nuovamente chown. Eseguite il logout e accedete nuovamente, oppure eseguite newgrp media, prima di verificare il vostro accesso. Un gruppo aggiunto con usermod -aG non è disponibile in una sessione di shell già aperta.
All'interno del container, groupmod -o -g 13000 abc assegna 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. Questo vale anche per i container più avanti nella catena, che leggono soltanto la libreria completata, come Jellyfin e le interfacce che si collegano a esso, ad esempio Halcyon, che presenta quella libreria come un videonoleggio degli anni 90 navigabile.
Impostate quindi UMASK=002 in ogni container linuxserver dello stack. Questo è il passaggio che spesso viene dimenticato. Se non è impostata, l'immagine non applica una umask propria. L'applicazione mantiene quindi la umask ereditata e il valore mostrato negli esempi dei README delle immagini è UMASK=022. Una umask 022 rimuove il bit di scrittura del gruppo. I file vengono quindi creati come 0644: il resto del gruppo può ancora leggerli, ma non può modificarli. Questo annulla gran parte della condivisione appena configurata. Con 002, un'applicazione che richiede la consueta 0666 per i file e 0777 per le directory ottiene file 0664 e, all'interno dell'albero setgid, directory 2775; il gruppo può quindi scrivere. Una umask rimuove soltanto i bit. Un'applicazione che richiede 0600 ottiene comunque 0600. Inoltre, una directory con una ACL predefinita ignora completamente la umask. Aggiungete la variabile accanto a PUID e PGID:
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-stoppedInserite questi due valori in un file .env accanto al file Compose, così l'intero stack legge un'unica definizione:
PUID=1000
PGID=13000Compose legge automaticamente quel file per la sostituzione in stile ${PUID}. È lo stesso meccanismo usato per le credenziali. Si applicano anche le indicazioni relative a mantenere i valori fuori da docker-compose.yml e inserirli in un file .env, con una differenza: questi due numeri non sono segreti.
Verificate il funzionamento dall'inizio alla fine invece di fidarvi della configurazione. Scrivete un file dall'interno di un container e leggetelo dall'host:
docker exec sonarr touch /data/downloads/permtest
ls -ln /srv/media/downloads/permtestUn risultato corretto mostra il vostro PUID come proprietario, 13000 come gruppo e -rw-rw-r-- come modalità. Se il gruppo visualizza 1000, eseguite ls -ldn /srv/media/downloads: nella directory manca il bit setgid, cioè il s in drwxrwsr-x, oppure la directory appartiene ancora al gruppo 1000 invece che a media. I comandi chown e chmod 2775 riportati sopra risolvono entrambi i problemi. Se la modalità visualizza -rw-r--r--, la variabile UMASK non è stata applicata. Verificate quindi di avere ricreato il container anziché riavviarlo. Al termine, rimuovete 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, e la documentazione indica di ricavarli da id -u e id -g. Anche i server fotografici mostrano la stessa varietà: PhotoPrism ha la propria coppia PHOTOPRISM_UID e PHOTOPRISM_GID, mentre Immich non fornisce un equivalente e lascia che l'utente del container venga determinato dalla chiave user: di Docker. Di conseguenza, scegliere tra PhotoPrism e Immich determina anche quale di questi meccanismi dovrai gestire per la libreria più grande del server. Molte immagini ufficiali upstream, comprese le comuni immagini per database e server web, includono un utente predefinito fisso e si aspettano che tu usi user: oppure che lo lasci invariato. Anche le installazioni di una singola applicazione presentano la stessa questione. Quando installi un tracker di allenamento openGym self-hosted, conviene verificare quale utente usa realmente il container prima di collegarvi un bind mount, perché la directory che contiene il database eredita quella configurazione indipendentemente dal fatto che tu imposti PUID. Lo stesso vale per un relay di accesso remoto. Quando esegui il tuo server relay RustDesk, la coppia di chiavi Ed25519 che hbbs scrive al primo avvio viene creata nel bind mount con la proprietà dell'utente con cui termina l'immagine. In seguito puoi correggere la proprietà con un chown eseguito sull'host oppure impostare in anticipo l'identità tramite la chiave user:, perché questa immagine non imposta un utente proprio, purché la directory montata sia scrivibile dall'ID scelto. Lo stesso vale per l'infrastruttura che aggiungi in seguito. Installare Authentik davanti alle applicazioni per usare un singolo accesso significa eseguire le immagini ufficiali di server, Postgres e Redis, che non leggono affatto PUID. La proprietà dei volumi dipende quindi dal runtime, non da un entrypoint configurabile.
Controlla quindi il README di ogni immagine prima di copiare un blocco di variabili d'ambiente tra progetti. Docker passa qualsiasi variabile d'ambiente impostata a qualsiasi container, indipendentemente dal fatto che qualcosa al suo interno la legga. Un PUID che nessun componente utilizza non produce errori, avvisi né effetti. Il container viene eseguito con l'utente definito dal proprio Dockerfile e puoi determinarlo osservando la proprietà dei file che scrive. Esegui questa verifica prima di aggiungere qualsiasi nuovo componente al server, incluso uno stack self-hosted open-kritt per la scansione di sicurezza, dove il file Compose indica se le immagini rispettano PUID oppure se la proprietà delle directory montate è fissata dalle immagini stesse.
FAQ
Perché i file Docker sono di proprietà di 911:911?
911 è l'UID e il GID dell'utente abc integrato nelle immagini linuxserver.io. Se compare questo valore, il container è stato avviato senza impostare PUID e PGID, quindi lo script di inizializzazione ha mantenuto i valori predefiniti integrati. ls -l mostra i numeri grezzi perché nessun account sull'host ha l'ID 911 e quindi non esiste 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 sulla directory interessata.
PUID e PGID funzionano con tutte le immagini Docker?
No. Non sono una funzionalità di Docker e Docker non li legge mai. Funzionano solo con le immagini il cui entrypoint li legge ed esegue usermod e groupmod prima di avviare l'applicazione. Questo riguarda la famiglia linuxserver.io e alcuni progetti che ne hanno copiato il modello. Altri progetti usano nomi diversi, come USERMAP_UID e USERMAP_GID in paperless-ngx. In un'immagine che non legge nessuna di queste variabili, i valori vengono accettati e ignorati 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, l'impostazione di user: rende inattivi PUID e PGID, impedisce l'esecuzione di Docker Mods e dei servizi personalizzati e rende a tuo carico i permessi di ogni volume montato.
Sonarr ha il PUID corretto, ma non riesce ancora a spostare i file. Qual è il problema?
Controlla tre aspetti, nell'ordine. Primo, il mount dei file multimediali: lo script di inizializzazione esegue chown solo su /app, /config e /defaults, quindi /data o /downloads mantengono la proprietà che hanno sull'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: con UMASK non impostato o impostato su 022, i nuovi file vengono creati come 0644 senza il bit di scrittura per il gruppo. Gli altri container del gruppo possono quindi leggerli, ma non modificarli. Imposta UMASK=002 e assegna il bit setgid alle directory con chmod 2775, così i nuovi file ereditano il gruppo.