SSD Nodes Learn 🎉 VPS ab $4.99/Monat
Anleitungen Matt ConnorVon Matt Connor

PUID und PGID in Docker Compose richtig verstehen

PUID und PGID sind keine Docker-Einstellungen, sondern eine Entrypoint-Konvention von linuxserver.io. Erfahren Sie, warum Dateien als 911:911 erscheinen.

Was PUID und PGID tatsächlich sind

PUID und PGID sind zwei Umgebungsvariablen, die bestimmte Container-Images beim Start auslesen. Docker selbst wertet sie niemals aus. Sie sind eine Konvention, die von linuxserver.io-Images und einigen anderen verwendet wird. Ein Image, das nicht dafür entwickelt wurde, ignoriert sie stillschweigend.

In einem linuxserver.io-Image gibt es einen Benutzer namens abc. Er wird beim Erstellen des Images mit der UID (Benutzer-ID) 911 und der GID (Gruppen-ID) 911 angelegt. Der Container startet als root und führt seine Initialisierungsskripte aus. Eines dieser Skripte ändert die IDs dieses Benutzers, bevor etwas anderes geschieht:

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

Das Flag -o erlaubt eine ID, die an anderer Stelle bereits verwendet wird. Danach verwirft die Initialisierung die Root-Rechte und führt die Anwendung als abc aus. PUID=1000 gelangt daher niemals zu Docker. Die Variable ändert die Benutzer-ID innerhalb des Containers, bevor die Anwendung startet. Deshalb wird jede Datei, die die Anwendung schreibt, auf Ihrer Festplatte mit dem Eigentümer 1000 angelegt. Wenn Sie PUID nicht setzen, behält abc die ID 911. Deshalb füllt ein nicht konfiguriertes Bind-Mount das Dateisystem mit Dateien, deren Eigentümer 911:911 ist.

Ermitteln Sie Ihre beiden Nummern mit id

Führen Sie dies auf dem Host als der Benutzer aus, dem die Datenverzeichnisse gehören:

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

uid ist Ihre PUID und gid ist Ihre PGID. Für ein Skript geben id -u und id -g nur die Nummern aus. Bei den meisten frisch bereitgestellten VPS-Images lautet das Konto des ersten Benutzers 1000:1000. Gehen Sie davon jedoch nicht aus. Ein neu aufgebauter Server oder ein später hinzugefügtes zweites Konto verwendet 1001 oder eine höhere Nummer. Eine falsche Nummer an dieser Stelle ist die gesamte Fehlerursache. Wenn Ihre Dienste unter einem dedizierten Dienstkonto statt unter Ihrem eigenen Anmeldekonto laufen, führen Sie id thatuser aus und übernehmen Sie die Nummern daraus.

Warum Ihre Dateien als 911:911 angezeigt werden

ls -l gibt eine numerische ID statt eines Namens aus, wenn kein Benutzerkonto auf dem Host zu dieser ID passt. Auf Ihrem Server gibt es keine UID 911. Daher kann kein Name ausgegeben werden. Verwenden Sie ls -ln, um immer die numerischen Werte anzuzeigen und die Zuordnung eindeutig zu machen:

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

Diese Ausgabe zeigt, dass der Container mit den integrierten Standardwerten gestartet wurde. Prüfen Sie dies direkt im Container, statt die Ursache zu vermuten:

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

Die linuxserver-Initialisierung schreibt ihr Ergebnis beim Start als zwei Zeilen in das Startprotokoll:

User UID:    911
User GID:    911

Wenn diese Zeilen nach dem Setzen von PUID=1000 in Ihrer Compose-Datei den Wert 911 anzeigen, ist die Variable nicht im Container angekommen. Die häufigste Ursache ist, dass Sie docker-compose.yml bearbeitet und anschließend docker compose restart ausgeführt haben. Dieser Befehl verwendet den vorhandenen Container mit seiner ursprünglichen Umgebung erneut. Änderungen an der Umgebung erfordern docker compose up -d. Dadurch wird der Container neu erstellt.

Warum Sie eine vom Container erstellte Datei nicht löschen können

Der Kernel vergleicht Nummern, niemals Namen. Ihre Shell läuft als UID 1000. Die Datei gehört UID 911. Das Verzeichnis, in dem sie liegt, ist drwxr-xr-x und gehört ebenfalls UID 911. Daher haben Gruppe und andere Benutzer Lese- und Ausführungsrechte, aber kein Schreibrecht. Zum Löschen einer Datei benötigen Sie Schreibrechte für ihr Verzeichnis, nicht für die Datei selbst. Deshalb erhalten Sie diesen Fehler, obwohl die Datei selbst unproblematisch aussieht:

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

Ein schreibender Container stößt von der anderen Seite auf dasselbe Problem. Wenn das Hostverzeichnis Ihrem Benutzer gehört und den Modus 755 hat, schlägt der erste Schreibvorgang der Anwendung mit Permission denied fehl, wenn sie als 911 läuft. Die Anwendung meldet den Fehler in ihrer eigenen Form. In einer .NET-Anwendung wie Sonarr oder Radarr wird er als UnauthorizedAccessException: Access to the path '/data/downloads' is denied angezeigt. Die Berechtigungszeichenfolge vor dem Dateinamen zeigt, anhand welcher der drei Berechtigungsmengen Sie tatsächlich bewertet werden. Wenn Sie drwxr-xr-x korrekt lesen, wird aus dem zunächst rätselhaften Fehler eine offensichtliche Ursache.

Dies ist speziell ein Problem mit einem Bind-Mount. Wenn Docker ein leeres benanntes Volume erstellt und es über einen im Image vorhandenen Pfad mountet, kopiert Docker den Inhalt dieses Pfads einschließlich Eigentümer und Berechtigungsbits in das Volume. Die Anwendung findet dadurch ein Verzeichnis vor, das ihr bereits gehört. Ein Bind-Mount erhält diese Behandlung nicht: Docker mountet Ihr Hostverzeichnis genau so, wie es vorliegt. Dieser Unterschied ist ein praktischer Grund dafür, zu wissen, wann ein Bind-Mount gegenüber einem benannten Volume vorzuziehen ist und wann nicht.

Verzeichnisberechtigungen korrigieren, wenn sie bereits falsch sind

Das Setzen von PUID und PGID ändert das Verhalten der Anwendung ab diesem Zeitpunkt. Bereits auf dem Datenträger vorhandene Dateien werden dadurch nicht nachträglich korrigiert. Stoppen Sie den Stack, korrigieren Sie den Eigentümer manuell und starten Sie den Stack anschließend erneut:

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

Verwenden Sie sudo chown -R "$(id -u):$(id -g)" /srv/appdata/sonarr, wenn Sie die Zahlen nicht eingeben möchten. Führen Sie den Befehl bei gestopptem Container aus. Andernfalls kann eine laufende Anwendung, die während eines rekursiven chown schreibt, einen teilweise korrigierten Verzeichnisbaum und eine weitere, schwer nachvollziehbare Fehlerwelle verursachen.

Was PUID und PGID nicht beheben

Hier liegt der Fall, der Benutzer trifft, die alles richtig gemacht haben. Das linuxserver-Init-Skript ändert beim Start mit chown genau drei Pfade: /app, /config und /defaults. Ihre Media-Mounts gehören nicht dazu. /data, /downloads und /tv werden unverändert an die Anwendung übergeben. Wenn der Host-Pfad dieser Mounts einem Benutzer gehört, für den der Container-Benutzer keine Schreibrechte besitzt, startet der Container ordnungsgemäß, gibt in seinem Banner die korrekte UID aus und schlägt anschließend beim ersten Import fehl.

Das ist das richtige Verhalten. Ein rekursives chown über eine zwölf Terabyte große Mediensammlung bei jedem Containerstart wäre ein Desaster. Das bedeutet jedoch, dass Sie für die Medienverzeichnisse zuständig sind. Bei diesen Mounts treten Berechtigungsprobleme tatsächlich auf.

Drei Möglichkeiten zur Steuerung des Benutzers und wann sie jeweils geeignet sind

Umgebungsvariablen PUID und PGID

Dies funktioniert nur bei Images, deren Entry Point diese Variablen ausliest. Die Methode ist verbreitet, weil der Container weiterhin als root startet, seine Einrichtung selbst durchführt, /config korrigiert und erst danach die Berechtigungen reduziert. Docker Mods und benutzerdefinierte Init-Skripte funktionieren weiterhin. Der Nachteil ist, dass Sie einer Konvention statt einer Plattformfunktion vertrauen. Außerdem sind die Variablennamen projektübergreifend nicht standardisiert.

Der Schlüssel user: in Compose

Dies ist eine echte Docker-Funktion und funktioniert mit jedem Image, weil die Container-Laufzeit sie anwendet, bevor der eigene Code des Images ausgeführt wird:

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

Der Prozess läuft niemals als root, auch nicht für einen kurzen Moment. Das ist ein echter Sicherheitsgewinn. Gleichzeitig funktionieren alle Schritte im Entry Point nicht mehr, die root-Berechtigungen benötigen. Bei linuxserver-Images unterstützt das Projekt diese Funktion nach bestem Bemühen und nur für Images, die es getestet hat. Die Einschränkungen sind konkret: PUID und PGID haben keine Wirkung mehr, Docker Mods werden nicht ausgeführt, benutzerdefinierte Dienste werden nicht gestartet, und Sie sind für die Berechtigungen auf allen eingebundenen Volumes verantwortlich. Das dokumentierte Muster kombiniert das Flag mit einem beschreibbaren /run:

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

Ein kosmetischer Nebeneffekt überrascht viele Benutzer. Ein numerischer user: hat keinen passenden Eintrag in /etc/passwd des Containers. Daher melden Tools im Container whoami: cannot find name for user ID 1000. Die ID ist gültig, und der Dateizugriff funktioniert normal. Nur die Namensauflösung schlägt fehl.

Rootless Docker

Rootless Docker führt den Daemon selbst unter Ihrem unprivilegierten Benutzer aus. Dadurch läuft auf dem System nichts als der tatsächliche root-Benutzer. Die Zuordnung von Besitzern ändert sich dadurch vollständig. Die Container-UID 0 wird der Host-UID des Benutzers zugeordnet, der Rootless Docker ausführt. Die Container-UID n für jedes n von 1 oder höher wird subuid + (n - 1) zugeordnet, wobei subuid die Basis des Ihnen in /etc/subuid zugewiesenen Bereichs und /etc/subgid ist. Docker erwartet dort mindestens 65,536 untergeordnete IDs.

Lesen Sie diese Zuordnung noch einmal, weil sie der üblichen Empfehlung widerspricht. Unter Rootless Docker erzeugt ein Container, der als root schreibt, Dateien, deren Besitzer Sie sind. Ein Container, der als UID 1000 schreibt, erzeugt Dateien, deren Besitzer eine untergeordnete ID ungefähr bei 100999 ist. Ihre Shell kann auf diese Dateien nicht zugreifen. Der PUID-Wert, der bei einem Daemon mit root-Berechtigungen korrekt ist, ist hier daher falsch. Beide Mechanismen lösen dasselbe Problem auf unterschiedlichen Ebenen. Wenn Sie sie kombinieren, ohne die Zuordnung zu prüfen, benötigen Sie am Ende möglicherweise sudo, um ein Verzeichnis zu löschen. Wenn Sie Rootless Docker verwenden, prüfen Sie auf Ihrem eigenen Server den Besitzer einer geschriebenen Datei, bevor Sie eine Bibliothek darin migrieren.

Für die meisten Self-Hosting-Stacks auf einem einzelnen VPS ist PUID und PGID mit einem Daemon mit root-Berechtigungen die pragmatische Wahl. Dafür sind die Images entwickelt und dokumentiert. Verwenden Sie user:, wenn die README des Images angibt, dass dieses Image dafür getestet wurde, oder wenn Sie ein offizielles Upstream-Image ohne PUID-Unterstützung einsetzen.

Der Fall eines Medien-Stacks: eine gemeinsame Gruppe für alle Container

Ein arr-Medien-Stack mit Sonarr, Radarr und einem Download-Client ist der Punkt, an dem dies praktisch relevant wird. Der Download-Client schreibt eine fertige Datei nach /data/downloads. Sonarr erstellt anschließend einen Hardlink auf diese Datei oder verschiebt sie nach /data/media. Damit der Hardlink funktioniert, benötigen beide Container Schreibzugriff auf denselben Verzeichnisbaum. Wenn der Download-Client jedoch als 1000 und Sonarr als 1001 ausgeführt wird, besitzt einer der beiden Container Dateien, die der andere nur lesen kann.

Die Lösung ist eine gemeinsame Gruppe, die jeder Container im Stack als PGID verwendet:

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 {} +

Das führende 2 in 2775 ist das setgid-Bit. Bei einem Verzeichnis bedeutet es, dass jede darin erstellte Datei und jedes Unterverzeichnis die Gruppe media statt der primären Gruppe des Erstellers übernimmt. Dadurch bleibt die Konfiguration auch bei neuen Downloads erhalten, ohne dass Sie chown erneut ausführen müssen. Melden Sie sich ab und wieder an oder führen Sie newgrp media aus, bevor Sie den eigenen Zugriff prüfen. Eine mit usermod -aG hinzugefügte Gruppe ist in einer bereits geöffneten Shell-Sitzung nicht verfügbar.

Im Container nummeriert groupmod -o -g 13000 abc die Gruppe abc auf 13000 um. Dadurch schreibt abc mit derselben GID wie Ihre Host-Gruppe media. Jeder Container im Stack behält seine eigene PUID und verwendet diese eine gemeinsame PGID.

Setzen Sie anschließend UMASK=002 für jeden linuxserver-Container im Stack. Diesen Schritt übersehen viele. Der Standardwert dieser Images ist UMASK=022. Er entfernt das Gruppenschreibrecht von jeder neu erstellten Datei. Die Dateien werden daher als 0644 angelegt, und die soeben eingerichtete gemeinsame Nutzung funktioniert nicht. 002 erstellt 0664-Dateien und 0775-Verzeichnisse, in die die Gruppe schreiben kann:

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

Diese beiden Werte gehören in eine .env-Datei neben die Compose-Datei, damit der gesamte Stack dieselbe Definition verwendet:

PUID=1000
PGID=13000

Compose liest diese Datei für die Substitution im ${PUID}-Stil automatisch ein. Dies ist derselbe Mechanismus, den Sie für Zugangsdaten verwenden. Die Regeln aus Werte aus docker-compose.yml herauszuhalten und in eine .env-Datei zu verschieben gelten auch hier. Der Unterschied besteht darin, dass diese beiden Zahlen keine Geheimnisse sind.

Überprüfen Sie die Konfiguration vollständig, statt ihr zu vertrauen. Schreiben Sie eine Datei aus einem Container heraus und lesen Sie sie anschließend vom Host:

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

Ein korrektes Ergebnis zeigt Ihre PUID als Eigentümer, 13000 als Gruppe und -rw-rw-r-- als Modus. Wenn die Gruppe als 1000 angezeigt wird, fehlt das setgid-Bit in diesem Verzeichnis. Wenn der Modus als -rw-r--r-- angezeigt wird, wurde die UMASK-Variable nicht wirksam. Prüfen Sie dann, ob Sie den Container neu erstellt und nicht nur neu gestartet haben. Löschen Sie die Testdatei anschließend mit rm /srv/media/downloads/permtest.

Welche Images verwenden welche Variable

Images von linuxserver.io verwenden PUID, PGID und UMASK. Paperless-ngx verwendet für dasselbe Konzept andere Namen: USERMAP_UID und USERMAP_GID, die beide standardmäßig auf 1000 gesetzt sind. In der Dokumentation wird angegeben, dass Sie diese Werte aus id -u und id -g auslesen sollen. Viele offizielle Upstream-Images, darunter die gängigen Datenbank- und Webserver-Images, enthalten einen fest vorgegebenen Benutzer und erwarten, dass Sie user: verwenden oder die Vorgabe unverändert lassen.

Prüfen Sie daher die README-Datei jedes Images, bevor Sie einen Umgebungsblock zwischen Projekten kopieren. Docker übergibt jede gesetzte Umgebungsvariable an jeden Container, unabhängig davon, ob ein Prozess darin sie auswertet. Ein PUID, das kein Prozess verwendet, erzeugt keinen Fehler und keine Warnung und hat keine Wirkung. Der Container läuft mit dem Benutzer, den sein eigenes Dockerfile am Ende festlegt. Anhand des Eigentümers der von ihm geschriebenen Dateien stellen Sie fest, welcher Benutzer das ist.

FAQ

Warum gehören meine Docker-Dateien 911:911?

911 ist die UID und GID des in linuxserver.io-Images integrierten Benutzers abc. Das bedeutet, dass der Container ohne gesetzte Variablen PUID und PGID gestartet wurde und das Init-Skript deshalb die integrierten Standardwerte beibehalten hat. ls -l zeigt die reinen Zahlen an, weil auf Ihrem Host kein Konto die ID 911 besitzt und daher kein Name angezeigt werden kann. Setzen Sie PUID und PGID auf die Ausgabe von id, erstellen Sie den Container mit docker compose up -d neu und korrigieren Sie anschließend die vorhandenen Dateien im betroffenen Verzeichnis mit sudo chown -R 1000:1000.

Funktionieren PUID und PGID mit jedem Docker-Image?

Nein. Sie sind keine Docker-Funktion, und Docker liest sie niemals aus. Sie funktionieren nur mit Images, deren eigenes Entrypoint-Skript sie ausliest und vor dem Start der Anwendung usermod und groupmod aufruft. Dazu gehören die linuxserver.io-Familie und einige Projekte, die dieses Muster übernommen haben. Andere Projekte verwenden andere Namen, beispielsweise USERMAP_UID und USERMAP_GID in paperless-ngx. Bei einem Image, das keine dieser Variablen ausliest, werden sie ohne Warnung akzeptiert und ignoriert.

Sollte ich PUID und PGID oder den Schlüssel user: in Docker Compose verwenden?

Verwenden Sie PUID und PGID, wenn das Image diese Variablen unterstützt. Das Entrypoint-Skript läuft dann lange genug als root, um /config zu korrigieren und die eigenen Dienste korrekt zu starten. Verwenden Sie user:, wenn das Image PUID nicht unterstützt oder wenn in der README des Images angegeben ist, dass der Betrieb ohne root getestet wurde. Bei einem linuxserver-Image macht das Setzen von user: PUID und PGID unwirksam, verhindert die Ausführung von Docker Mods und benutzerdefinierten Diensten und überlässt Ihnen die Verantwortung für die Berechtigungen aller eingebundenen Volumes.

Sonarr hat die richtige PUID, kann Dateien aber weiterhin nicht verschieben. Was ist falsch?

Prüfen Sie drei Punkte in dieser Reihenfolge. Erstens das Medien-Mount selbst: Das Init-Skript ändert nur den Eigentümer von /app, /config und /defaults. Daher behält /data oder /downloads die Eigentümerschaft des Hosts bei. Zweitens die gemeinsame Gruppe: Wenn der Download-Client und Sonarr mit unterschiedlichen GIDs laufen, kann keiner die Dateien des anderen ändern. Weisen Sie deshalb jedem Container im Stack dieselbe PGID zu. Drittens die umask: Der Image-Standardwert UMASK=022 erstellt Dateien als 0644 ohne Schreibberechtigung für die Gruppe. Dadurch wird eine gemeinsame Gruppe vollständig unwirksam. Setzen Sie UMASK=002 und setzen Sie das setgid-Bit auf den Verzeichnissen mit chmod 2775, damit neue Dateien die Gruppe erben.