SSD Nodes Learn 🎉 VPS vanaf $5.50/mnd
Gidsen Matt ConnorDoor Matt Connor · Bijgewerkt 2026-08-13

Wat doen PUID en PGID in Docker Compose?

PUID en PGID zijn geen Docker-instellingen, maar conventies van linuxserver.io. Ontdek waarom bestanden als 911:911 verschijnen en hoe u dit correct instelt voor uw containers.

Wat PUID en PGID daadwerkelijk zijn

PUID en PGID zijn twee omgevingsvariabelen die bepaalde container-images bij het opstarten uitlezen. Docker zelf kijkt hier nooit naar. Het is een conventie die wordt gebruikt door linuxserver.io-images en een handvol andere; een image die niet is geschreven om deze te lezen, negeert ze geruisloos.

Binnen een linuxserver.io-image bevindt zich een gebruiker genaamd abc, die tijdens het build-proces is aangemaakt met UID (user ID) 911 en GID (group ID) 911. De container start als root, voert zijn init-scripts uit, en een van die scripts hernummert die gebruiker voordat er iets anders gebeurt:

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

De -o-vlag staat een ID toe die elders al in gebruik is. Daarna verlaagt het init-proces de rechten en voert het de applicatie uit als abc. Dus PUID=1000 bereikt Docker nooit. De variabele hernummert een gebruiker binnen de container voordat de applicatie start, wat betekent dat elk bestand dat de applicatie schrijft op uw schijf terechtkomt met eigenaar 1000. Laat PUID oningesteld en abc behoudt 911; dit is de reden waarom een niet-geconfigureerde bind mount volloopt met bestanden die eigendom zijn van 911:911.

Verkrijg uw twee nummers met id

Voer dit uit op de host, als de gebruiker die eigenaar is van de datamappen:

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

uid is uw PUID en gid is uw PGID. Voor een script printen id -u en id -g enkel de nummers. Op de meeste nieuwe VPS-images is het eerste menselijke account 1000:1000, maar ga hier niet zomaar vanuit. Een opnieuw geïnstalleerde server, of een tweede account dat later is toegevoegd, geeft 1001 of hoger, en een onjuist nummer hier is de volledige oorzaak van de fout. Als uw services draaien onder een toegewezen service-account in plaats van uw eigen inlogaccount, voer dan id thatuser uit en neem de nummers daarvandaan.

Waarom uw bestanden worden weergegeven als 911:911

ls -l toont een numeriek ID in plaats van een naam wanneer er geen host-account overeenkomt met dat ID. Niets op uw server heeft UID 911, dus er is geen naam om weer te geven. Gebruik ls -ln om altijd nummers te zien en de ambiguïteit weg te nemen:

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

Die output geeft aan dat de container is uitgevoerd met de ingebouwde standaardinstellingen. Controleer dit vanuit de container in plaats van te gissen:

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

De linuxserver init toont het resultaat in het opstartlogboek als twee regels:

User UID:    911
User GID:    911

Als die regels 911 aangeven nadat u PUID=1000 in uw Compose-bestand heeft ingesteld, heeft de variabele de container nooit bereikt. De gebruikelijke oorzaak is dat u docker-compose.yml heeft bewerkt en vervolgens docker compose restart heeft uitgevoerd, wat de bestaande container met de oorspronkelijke omgeving hergebruikt. Omgevingswijzigingen vereisen docker compose up -d, waarmee de container opnieuw wordt aangemaakt.

Waarom u een bestand dat door de container is geschreven niet kunt verwijderen

De kernel vergelijkt nummers, nooit namen. Uw shell draait als UID 1000. Het bestand is eigendom van UID 911. De map waarin het zich bevindt is drwxr-xr-x en is eveneens eigendom van 911, waardoor de groep en anderen wel lees- en uitvoerrechten hebben, maar geen schrijfrechten. Voor het verwijderen van een bestand is schrijftoegang tot de map vereist, niet tot het bestand zelf. Daarom krijgt u deze foutmelding, zelfs als het bestand er onschadelijk uitziet:

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

Een schrijvende container loopt aan de andere kant tegen hetzelfde probleem aan. Als de hostmap eigendom is van uw gebruiker met modus 755 en de applicatie draait als 911, mislukt de eerste schrijfactie met Permission denied en rapporteert de applicatie dit in eigen bewoordingen. In een .NET-applicatie zoals Sonarr of Radarr verschijnt dit als UnauthorizedAccessException: Access to the path '/data/downloads' is denied. De rechtenreeks voor het bestand geeft aan door welke van de drie rechtenreeksen u daadwerkelijk wordt beoordeeld, en het correct lezen van drwxr-xr-x verandert die foutmelding van mysterieus in overduidelijk.

Dit is specifiek een probleem met bind mounts. Wanneer Docker een leeg named volume aanmaakt en dit koppelt over een pad dat in de image bestaat, kopieert het de inhoud van dat pad naar het volume, inclusief eigenaarschap en rechten, zodat de applicatie een map vindt waarvan het al de eigenaar is. Een bind mount krijgt die behandeling niet: Docker koppelt uw hostmap exact zoals deze is. Dat verschil is een van de praktische redenen om te weten wanneer een bind mount de voorkeur verdient boven een named volume en wanneer niet.

Een map corrigeren die al onjuist is

Het instellen van PUID en PGID verandert het gedrag van de applicatie vanaf dat moment. Het herstelt niet met terugwerkende kracht bestanden die al op de schijf staan. Stop de stack, corrigeer het eigenaarschap zelf en start de stack daarna opnieuw:

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

Gebruik sudo chown -R "$(id -u):$(id -g)" /srv/appdata/sonarr als u de getallen liever niet handmatig intypt. Voer dit uit terwijl de container is gestopt. Een actieve applicatie die midden in een schrijfproces zit tijdens een recursieve chown kan resulteren in een gedeeltelijk gecorrigeerde mappenstructuur en een verwarrende tweede reeks foutmeldingen.

Wat PUID en PGID niet oplossen

Dit is het punt waar gebruikers vastlopen die verder alles correct hebben ingesteld. Het linuxserver-init-script voert bij het opstarten een chown uit op precies drie paden: /app, /config en /defaults. Uw media-mounts staan niet op die lijst. /data, /downloads en /tv worden ongewijzigd aan de applicatie doorgegeven. Als de host-zijde van die mounts eigendomsrechten heeft waar de container-gebruiker niet naar kan schrijven, start de container weliswaar correct op en toont de juiste UID in de banner, maar faalt deze bij de eerste import.

Dit is het juiste gedrag. Een recursieve chown over een mediabibliotheek van twaalf terabyte bij elke start van de container zou een ramp zijn. Het betekent wel dat het beheer van de media-mappen uw verantwoordelijkheid is; dit zijn de mounts waar rechten daadwerkelijk fout kunnen gaan.

Three ways to control the user, and when each one applies

PUID and PGID environment variables

This works only on images whose entrypoint reads them. It is popular because the container still starts as root, does its own setup, fixes /config, and only then drops privileges. Docker Mods and custom init scripts keep working. The cost is that you are trusting a convention rather than a platform feature, and the variable names are not standard across projects.

The user: key in Compose

This one is a real Docker feature and works on every image, because the container runtime applies it before the image's own code runs:

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

The process never runs as root, not even for a moment, which is a genuine security gain. It also breaks anything in the entrypoint that needed root. On linuxserver images the project supports this on a reasonable endeavours basis and only for images it has tested, and the caveats are specific: PUID and PGID stop having any effect, Docker Mods will not run, custom services will not run, and you become responsible for the permissions on every mounted volume. Their documented pattern pairs the flag with a writable /run:

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

One cosmetic side effect surprises people. A numeric user: has no matching entry in the container's /etc/passwd, so tools inside report whoami: cannot find name for user ID 1000. The ID is valid and file access works normally. Only the name lookup fails.

Rootless Docker

Rootless Docker runs the daemon itself as your unprivileged user, so nothing on the box runs as real root. It changes the ownership arithmetic completely. Container UID 0 maps to the host UID of the user running rootless Docker, and container UID n for any n of 1 or more maps to subuid + (n - 1), where subuid is the base of the range allocated to you in /etc/subuid and /etc/subgid. Docker expects at least 65,536 subordinate IDs there.

Read that mapping again, because it inverts the usual advice. Under rootless Docker a container writing as root produces files owned by you. A container writing as UID 1000 produces files owned by a subordinate ID somewhere around 100999, which your shell cannot touch. So the PUID value that is correct on a rootful daemon is the wrong one here. The two mechanisms solve the same problem in different layers, and stacking them without checking is how people end up with a directory they need sudo to remove. If you go rootless, test the ownership of one written file on your own server before you migrate a library into it.

For most self-hosted stacks on a single VPS, PUID and PGID on a rootful daemon is the pragmatic choice, because it is what the images are built and documented for. Reach for user: when the image README says that image is tested for it, or when you are running an official upstream image that has no PUID support at all. A document workspace such as a self-hosted AFFiNE instance on one VPS falls into that last case, because none of its containers read PUID and the ownership of its database directory and uploaded files is settled by the runtime rather than by anything in the environment block.

De media stack-case: één groep gedeeld over containers

Een arr media stack met Sonarr, Radarr en een downloadclient is waar dit ophoudt theorie te zijn. De downloadclient schrijft een voltooid bestand naar /data/downloads. Sonarr maakt vervolgens een hardlink of verplaatst dat bestand naar /data/media. Om de hardlink te laten werken, hebben beide containers schrijftoegang nodig tot dezelfde boomstructuur. Als de downloadclient draait als 1000 terwijl Sonarr draait als 1001, bezit een van hen bestanden die de ander alleen kan lezen.

De oplossing is een gedeelde groep die elke container in de stack gebruikt als zijn 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 {} +

De voorloop-2 in 2775 is de setgid-bit. Op een map betekent dit dat elk nieuw bestand en elke submap die erin wordt aangemaakt, de groep media erft in plaats van de primaire groep van de maker. Hierdoor blijft de configuratie behouden bij nieuwe downloads zonder dat u chown opnieuw hoeft uit te voeren. Log uit en weer in, of voer newgrp media uit, voordat u uw eigen toegang controleert: een groep die is toegevoegd met usermod -aG verschijnt niet in een reeds geopende shell-sessie.

Binnen de container hernummert groupmod -o -g 13000 abc de abc-groep naar 13000, zodat abc schrijft met dezelfde GID als uw host media-groep. Elke container in de stack behoudt zijn eigen PUID en deelt die ene PGID.

Stel vervolgens UMASK=002 in op elke linuxserver-container in de stack. Dit is de stap die mensen vaak vergeten. De standaardwaarde in deze images is UMASK=022, wat de groepsschrijfbit van elk nieuw bestand verwijdert. Hierdoor krijgen bestanden de rechten 0644 en doet de zojuist geconfigureerde deling niets. 002 produceert 0664-bestanden en 0775-mappen, en de groep kan schrijven:

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

Deze twee waarden horen thuis in een .env-bestand naast het Compose-bestand, zodat de hele stack één definitie leest:

PUID=1000
PGID=13000

Compose leest dat bestand automatisch voor ${PUID}-stijl substitutie, wat hetzelfde mechanisme is dat u gebruikt voor inloggegevens. De gewoontes rondom het buiten het docker-compose.yml-bestand houden van waarden in een .env-bestand zijn hier ook van toepassing, met het verschil dat deze twee getallen niet geheim zijn.

Controleer het van begin tot eind in plaats van op de configuratie te vertrouwen. Schrijf een bestand vanuit één container en lees het vanaf de host:

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

Een correct resultaat toont uw PUID als eigenaar, 13000 als groep en -rw-rw-r-- als modus. Als de groep 1000 leest, ontbreekt de setgid-bit op die map. Als de modus -rw-r--r-- leest, is de UMASK-variabele niet effectief geworden; controleer dus of u de container opnieuw hebt aangemaakt in plaats van alleen herstart. Verwijder het testbestand met rm /srv/media/downloads/permtest wanneer u klaar bent.

Welke images gebruiken welke variabele

linuxserver.io images gebruiken PUID, PGID en UMASK. Paperless-ngx gebruikt andere namen voor hetzelfde concept: USERMAP_UID en USERMAP_GID, die beide standaard op 1000 staan, en de documentatie adviseert deze te lezen uit id -u en id -g. Fotoservers vertonen dezelfde variatie: PhotoPrism heeft zijn eigen PHOTOPRISM_UID en PHOTOPRISM_GID paar, terwijl Immich geen equivalent meelevert en de containergebruiker overlaat aan de user: key van Docker. Daarom bepaalt de keuze tussen PhotoPrism en Immich ook welk van deze mechanismen u zult onderhouden voor de grootste bibliotheek op de server. Veel officiële upstream-images, waaronder de gangbare database- en webserver-images, leveren een vaste ingebouwde gebruiker mee en verwachten dat u user: gebruikt of de instelling ongewijzigd laat. Hetzelfde geldt voor infrastructuur die u later toevoegt; het implementeren van Authentik voor uw applicaties voor een centrale login betekent dat u officiële server-, Postgres- en Redis-images draait die geen PUID lezen. Het eigenaarschap van hun volumes wordt bepaald door de runtime in plaats van door een entrypoint dat u kunt configureren.

Controleer daarom de README van elk image voordat u een environment-blok kopieert tussen projecten. Docker geeft elke omgevingsvariabele die u instelt door aan elke container, ongeacht of de inhoud ervan deze leest of niet. Een PUID die door niets wordt verbruikt, produceert geen foutmelding, geen waarschuwing en heeft geen effect. De container draait als de gebruiker waarmee het eigen Dockerfile is geëindigd, en u komt hierachter door het eigenaarschap van de bestanden die de container schrijft.

FAQ

Waarom zijn mijn Docker-bestanden eigendom van 911:911?

911 is de UID en GID van de abc-gebruiker die is ingebouwd in linuxserver.io-images. Dit betekent dat de container is gestart zonder dat PUID en PGID zijn ingesteld, waardoor het init-script de ingebouwde standaardwaarden heeft behouden. ls -l toont de ruwe getallen omdat er op uw host geen account met ID 911 bestaat, waardoor er geen naam kan worden weergegeven. Stel PUID en PGID in op de uitvoer van id, maak de container opnieuw aan met docker compose up -d en herstel vervolgens de bestaande bestanden met sudo chown -R 1000:1000 in de betreffende map.

Werken PUID en PGID op elk Docker-image?

Nee. Dit is geen Docker-functie en Docker leest deze variabelen nooit. Ze werken alleen bij images waarvan het eigen entrypoint ze leest en usermod en groupmod aanroept voordat de applicatie wordt gestart. Dit geldt voor de linuxserver.io-familie en enkele projecten die dit patroon hebben overgenomen. Andere projecten gebruiken andere namen, zoals USERMAP_UID en USERMAP_GID in paperless-ngx. Bij een image dat geen van beide leest, worden de variabelen geaccepteerd en zonder waarschuwing genegeerd.

Moet ik PUID en PGID gebruiken of de user:-sleutel in Docker Compose?

Gebruik PUID en PGID wanneer het image deze ondersteunt, omdat het entrypoint lang genoeg als root draait om /config te herstellen en de eigen services correct te starten. Gebruik user: wanneer het image geen PUID-ondersteuning heeft, of wanneer in de README van het image staat dat het getest is voor gebruik zonder root-rechten. Bij een linuxserver-image zorgt het instellen van user: ervoor dat PUID en PGID inactief worden, stopt het de uitvoering van Docker Mods en aangepaste services, en worden de rechten van elk gekoppeld volume uw eigen verantwoordelijkheid.

Sonarr heeft de juiste PUID, maar kan nog steeds geen bestanden verplaatsen. Wat is er mis?

Controleer de volgende drie zaken in deze volgorde. Ten eerste de media-mount zelf: het init-script voert alleen een chown uit op /app, /config en /defaults, waardoor /data of /downloads de eigendomsrechten behoudt die het op de host heeft. Ten tweede de gedeelde groep: als de downloadclient en Sonarr onder verschillende GID's draaien, kan geen van beide de bestanden van de ander wijzigen; geef daarom elke container in de stack dezelfde PGID. Ten derde de umask: de standaardwaarde van het image UMASK=022 schrijft bestanden als 0644 zonder schrijfrechten voor de groep, wat een gedeelde groep volledig nutteloos maakt. Stel UMASK=002 in en zet de setgid-bit op de mappen met chmod 2775, zodat nieuwe bestanden de groep overerven.