SSD Nodes Learn 🎉 VPS dès $4.99/mois
Guides Matt ConnorPar Matt Connor

PUID et PGID dans Docker Compose : à quoi servent-ils ?

PUID et PGID ne sont pas des paramètres Docker : ce sont une convention des images linuxserver.io. Découvrez pourquoi vos fichiers portent 911:911 et comment corriger cela.

Ce que sont réellement PUID et PGID

PUID et PGID sont deux variables d’environnement que certaines images de conteneur lisent au démarrage. Docker lui-même ne les consulte jamais. Il s’agit d’une convention utilisée par les images linuxserver.io et quelques autres. Une image qui n’a pas été conçue pour les lire les ignore silencieusement.

Dans une image linuxserver.io, un utilisateur appelé abc est créé lors du build avec l’UID (user ID) 911 et le GID (group ID) 911. Le conteneur démarre en tant que root, exécute ses scripts d’initialisation, puis l’un de ces scripts modifie l’UID et le GID de cet utilisateur avant toute autre opération :

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

L’option -o autorise l’utilisation d’un ID déjà utilisé ailleurs. Ensuite, le script d’initialisation abandonne les privilèges root et lance l’application en tant que abc. Ainsi, PUID=1000 n’est jamais transmis à Docker. La variable modifie l’UID et le GID d’un utilisateur dans le conteneur avant le démarrage de l’application. Tous les fichiers écrits par l’application sur votre disque appartiennent donc à l’utilisateur 1000. Si vous ne définissez pas PUID, abc conserve la valeur 911. C’est pourquoi un bind mount non configuré se remplit de fichiers appartenant à 911:911.

Obtenez vos deux identifiants avec id

Exécutez cette commande sur l’hôte, avec l’utilisateur propriétaire des répertoires de données :

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

uid correspond à votre PUID et gid à votre PGID. Dans un script, id -u et id -g affichent uniquement les valeurs numériques. Sur la plupart des images VPS fraîchement installées, le premier compte utilisateur est 1000:1000, mais ne le supposez pas. Un serveur reconstruit ou un deuxième compte ajouté ultérieurement peut utiliser 1001 ou une valeur supérieure. Une valeur incorrecte ici suffit à provoquer tout le problème. Si vos services s’exécutent avec un compte de service dédié plutôt qu’avec votre propre compte de connexion, exécutez id thatuser et utilisez les valeurs obtenues.

Pourquoi vos fichiers apparaissent avec l’UID et le GID 911:911

ls -l affiche un identifiant numérique lorsqu’aucun compte de l’hôte ne correspond à cet identifiant. Aucun compte de votre serveur ne possède l’UID 911, donc aucun nom ne peut être affiché. Utilisez ls -ln pour afficher systématiquement les identifiants numériques et éviter toute ambiguïté :

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

Cette sortie indique que le conteneur a utilisé les valeurs par défaut intégrées. Vérifiez-le depuis l’intérieur du conteneur au lieu de vous baser sur des suppositions :

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

L’init de linuxserver affiche son résultat dans le startup log, sur deux lignes :

User UID:    911
User GID:    911

Si ces lignes indiquent 911 après avoir défini PUID=1000 dans votre fichier Compose, la variable n’a jamais été transmise au conteneur. La cause la plus courante est d’avoir modifié docker-compose.yml, puis exécuté docker compose restart, qui réutilise le conteneur existant avec son environnement initial. Les changements d’environnement nécessitent docker compose up -d, qui recrée le conteneur.

Pourquoi vous ne pouvez pas supprimer un fichier écrit par le conteneur

Le noyau compare des numéros, jamais des noms. Votre shell s’exécute avec l’UID 1000. Le fichier appartient à l’UID 911. Le répertoire qui le contient est drwxr-xr-x et appartient également à 911. Le groupe et les autres utilisateurs disposent donc des permissions de lecture et d’exécution, mais pas d’écriture. Pour supprimer un fichier, il faut disposer de la permission d’écriture sur son répertoire, et non sur le fichier. Vous obtenez donc ceci même lorsque le fichier lui-même semble inoffensif :

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

Un conteneur qui écrit rencontre le même problème dans l’autre sens. Si le répertoire de l’hôte appartient à votre utilisateur avec le mode 755 et que l’application s’exécute avec l’UID 911, sa première écriture échoue avec Permission denied. L’application le signale avec ses propres termes. Dans une application .NET telle que Sonarr ou Radarr, cela apparaît sous la forme UnauthorizedAccessException: Access to the path '/data/downloads' is denied. La chaîne de permissions placée devant le fichier indique lequel des trois ensembles de permissions est réellement utilisé pour vous évaluer. Lire correctement drwxr-xr-x permet de transformer cette erreur mystérieuse en problème évident.

Il s’agit précisément d’un problème de bind mount. Lorsque Docker crée un named volume vide et le monte sur un chemin qui existe dans l’image, il copie le contenu de ce chemin dans le volume, y compris le propriétaire et les bits de permission. L’application trouve donc un répertoire dont elle est déjà propriétaire. Un bind mount ne bénéficie d’aucun traitement de ce type : Docker monte votre répertoire de l’hôte exactement tel qu’il est. Cette différence explique notamment pourquoi il est utile de savoir quand un bind mount est préférable à un named volume et quand il ne l’est pas.

Corriger un répertoire dont les permissions sont déjà incorrectes

La définition de PUID et PGID modifie le fonctionnement de l’application à partir de maintenant. Elle ne corrige pas rétroactivement les fichiers déjà présents sur le disque. Arrêtez la stack, corrigez vous-même le propriétaire, puis redémarrez-la :

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

Utilisez sudo chown -R "$(id -u):$(id -g)" /srv/appdata/sonarr si vous préférez ne pas saisir les nombres. Effectuez cette opération lorsque le conteneur est arrêté. Une application en cours d’écriture pendant un chown récursif peut laisser une arborescence partiellement corrigée et provoquer une nouvelle série d’erreurs difficiles à interpréter.

Ce que PUID et PGID ne corrigent pas

Voici le point qui concerne les personnes ayant pourtant tout configuré correctement. Le script d’initialisation linuxserver exécute chown sur exactement trois chemins au démarrage : /app, /config et /defaults. Vos montages de médias n’en font pas partie. /data, /downloads et /tv sont transmis à l’application sans modification. Si le côté hôte de ces montages appartient à un utilisateur auquel l’utilisateur du conteneur ne peut pas écrire, le conteneur démarre correctement, affiche le bon UID dans sa bannière, puis échoue lors du premier import.

C’est le comportement attendu. Exécuter un chown récursif chown sur une bibliothèque de médias de douze téraoctets à chaque démarrage du conteneur serait désastreux. En revanche, cela signifie que la gestion des répertoires de médias vous revient. Ce sont ces montages qui posent réellement des problèmes de permissions.

Trois façons de contrôler l’utilisateur, et dans quels cas les utiliser

Variables d’environnement PUID et PGID

Cette méthode fonctionne uniquement avec les images dont l’entrypoint les lit. Elle est populaire, car le conteneur démarre quand même en tant que root, effectue sa propre initialisation, corrige /config, puis abandonne les privilèges. Docker Mods et les scripts d’initialisation personnalisés continuent de fonctionner. En contrepartie, vous faites confiance à une convention plutôt qu’à une fonctionnalité de la plate-forme, et les noms de variables ne sont pas standardisés entre les projets.

La clé user: dans Compose

Il s’agit d’une véritable fonctionnalité Docker qui fonctionne avec toutes les images, car le runtime du conteneur l’applique avant l’exécution du code de l’image :

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

Le processus ne s’exécute jamais en tant que root, même brièvement. C’est un réel gain de sécurité. En revanche, cette méthode casse tout ce qui nécessite les privilèges root dans l’entrypoint. Pour les images linuxserver, le projet prend cette configuration en charge dans la mesure du raisonnable et uniquement pour les images qu’il a testées. Les restrictions sont précises : PUID et PGID n’ont plus aucun effet, Docker Mods ne s’exécutera pas, les services personnalisés ne s’exécuteront pas, et vous devez gérer les permissions de chaque volume monté. Le modèle documenté associe cette option à un /run accessible en écriture :

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

Un effet purement cosmétique surprend certains utilisateurs. Un user: numérique ne possède aucune entrée correspondante dans le /etc/passwd du conteneur. Les outils internes signalent donc whoami: cannot find name for user ID 1000. L’ID est valide et l’accès aux fichiers fonctionne normalement. Seule la résolution du nom échoue.

Docker rootless

Docker rootless exécute le daemon lui-même avec votre utilisateur non privilégié. Rien sur la machine ne s’exécute donc réellement en tant que root. Cela modifie complètement le calcul des propriétaires. L’UID 0 du conteneur est mappé vers l’UID de l’utilisateur qui exécute Docker rootless sur l’hôte. L’UID du conteneur n, pour toute n égale ou supérieure à 1, est mappée vers subuid + (n - 1), où subuid correspond à la base de la plage qui vous est attribuée dans /etc/subuid et /etc/subgid. Docker attend au moins 65,536 IDs secondaires dans ce fichier.

Relisez attentivement ce mapping, car il inverse les recommandations habituelles. Avec Docker rootless, un conteneur qui écrit en tant que root crée des fichiers dont vous êtes propriétaire. Un conteneur qui écrit avec l’UID 1000 crée des fichiers appartenant à un ID secondaire situé aux alentours de 100999, auxquels votre shell ne peut pas accéder. La valeur PUID correcte avec un daemon rootful est donc incorrecte dans ce cas. Les deux mécanismes résolvent le même problème, mais à des niveaux différents. Les utiliser ensemble sans vérifier leur fonctionnement est le meilleur moyen de se retrouver avec un répertoire qu’il faut sudo pour supprimer. Si vous passez à rootless, testez la propriété d’un fichier créé sur votre propre serveur avant d’y migrer une bibliothèque.

Pour la plupart des stacks auto-hébergées sur un VPS unique, PUID et PGID avec un daemon rootful constituent le choix pragmatique, car c’est la configuration pour laquelle les images sont conçues et documentées. Utilisez user: lorsque le README de l’image indique qu’elle a été testée avec cette option, ou lorsque vous exécutez une image officielle upstream qui ne prend pas du tout en charge PUID.

Le cas d’une stack multimédia : un groupe partagé entre les conteneurs

Une stack multimédia arr avec Sonarr, Radarr et un client de téléchargement permet de vérifier que ce problème est concret. Le client de téléchargement écrit le fichier terminé dans /data/downloads. Sonarr crée ensuite un hardlink vers ce fichier ou le déplace dans /data/media. Pour que le hardlink fonctionne, les deux conteneurs doivent avoir un accès en écriture au même arbre. Si le client de téléchargement s’exécute avec l’UID 1000 et Sonarr avec l’UID 1001, l’un des conteneurs possède les fichiers que l’autre peut seulement lire.

La solution consiste à utiliser un groupe partagé dont chaque conteneur de la stack utilise le 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 {} +

Le 2 initial dans 2775 correspond au bit setgid. Sur un répertoire, cela signifie que chaque nouveau fichier et sous-répertoire créé à l’intérieur hérite du groupe media au lieu d’hériter du groupe principal de son créateur. Cette configuration reste donc valable pour les nouveaux téléchargements sans devoir réexécuter chown. Déconnectez-vous puis reconnectez-vous, ou exécutez newgrp media, avant de vérifier votre propre accès : un groupe ajouté avec usermod -aG n’apparaît pas dans une session shell déjà ouverte.

Dans le conteneur, groupmod -o -g 13000 abc renumérote le groupe abc avec le GID 13000. Ainsi, abc écrit avec le même GID que votre groupe media sur l’hôte. Chaque conteneur de la stack conserve son propre PUID et partage ce PGID.

Définissez ensuite UMASK=002 dans chaque conteneur linuxserver de la stack. C’est l’étape souvent oubliée. La valeur par défaut de ces images est UMASK=022. Elle retire le bit d’écriture du groupe pour chaque nouveau fichier. Les fichiers sont donc créés avec les permissions 0644, et le partage que vous venez de configurer ne sert à rien. 002 crée des fichiers 0664 et des répertoires 0775, que le groupe peut modifier :

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

Ces deux valeurs doivent figurer dans un fichier .env placé à côté du fichier Compose, afin que toute la stack utilise une seule définition :

PUID=1000
PGID=13000

Compose lit automatiquement ce fichier pour effectuer la substitution de variables de style ${PUID}. C’est le même mécanisme que celui utilisé pour les identifiants. Les règles qui consistent à conserver les valeurs hors de docker-compose.yml, dans un fichier .env s’appliquent également ici. La différence est que ces deux nombres ne sont pas secrets.

Vérifiez le fonctionnement de bout en bout au lieu de vous fier à la configuration. Écrivez un fichier depuis un conteneur, puis lisez-le depuis l’hôte :

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

Un résultat correct affiche votre PUID comme propriétaire, 13000 comme groupe et -rw-rw-r-- comme mode. Si le groupe affiché est 1000, le bit setgid manque sur ce répertoire. Si le mode affiché est -rw-r--r--, la variable UMASK n’a pas été prise en compte. Vérifiez donc que vous avez recréé le conteneur au lieu de simplement le redémarrer. Supprimez le fichier de test avec rm /srv/media/downloads/permtest lorsque vous avez terminé.

Quelles images utilisent quelles variables

Les images linuxserver.io utilisent PUID, PGID et UMASK. Paperless-ngx utilise d’autres noms pour la même information : USERMAP_UID et USERMAP_GID, tous deux définis par défaut à 1000. Sa documentation vous indique de les lire depuis id -u et id -g. De nombreuses images officielles en amont, notamment les images courantes de bases de données et de serveurs web, utilisent un utilisateur intégré fixe et attendent que vous utilisiez user: ou que vous le laissiez inchangé.

Consultez donc le README de chaque image avant de copier un bloc d’environnement entre plusieurs projets. Docker transmet chaque variable d’environnement que vous définissez à chaque conteneur, qu’un processus à l’intérieur la lise ou non. Une PUID que rien n’utilise ne produit aucune erreur, aucun avertissement et aucun effet. Le conteneur s’exécute avec l’utilisateur défini à la fin de son propre Dockerfile. Vous pouvez le déterminer en examinant le propriétaire des fichiers qu’il écrit.

FAQ

Pourquoi mes fichiers Docker appartiennent-ils à 911:911 ?

911 est l’UID et le GID de l’utilisateur abc intégré aux images linuxserver.io. Cela indique que le conteneur a démarré sans que PUID et PGID soient définis. Son script d’initialisation a donc conservé les valeurs par défaut intégrées. ls -l affiche les nombres bruts, car aucun compte de votre hôte n’a l’ID 911. Aucun nom ne peut donc être affiché. Définissez PUID et PGID avec la sortie de id, recréez le conteneur avec docker compose up -d, puis corrigez les fichiers existants avec sudo chown -R 1000:1000 dans le répertoire concerné.

PUID et PGID fonctionnent-ils avec toutes les images Docker ?

Non. Ce ne sont pas des fonctionnalités de Docker, et Docker ne les lit jamais. Ils fonctionnent uniquement avec les images dont le propre entrypoint les lit, puis appelle usermod et groupmod avant de démarrer l’application. C’est le cas de la famille linuxserver.io et de quelques projets qui ont repris ce modèle. D’autres projets utilisent d’autres noms, comme USERMAP_UID et USERMAP_GID dans paperless-ngx. Avec une image qui ne lit aucune de ces variables, elles sont acceptées puis ignorées sans avertissement.

Dois-je utiliser PUID et PGID ou la clé user: dans Docker Compose ?

Utilisez PUID et PGID lorsque l’image les prend en charge. L’entrypoint s’exécute ainsi encore assez longtemps avec root pour corriger /config et démarrer correctement ses propres services. Utilisez user: lorsque l’image ne prend pas en charge PUID, ou lorsque son README indique qu’elle est testée en fonctionnement sans root. Avec une image linuxserver, la définition de user: rend PUID et PGID inactifs, empêche l’exécution des Docker Mods et des services personnalisés, et vous rend responsable des permissions de chaque volume monté.

Sonarr utilise le bon PUID, mais ne peut toujours pas déplacer les fichiers. Quel est le problème ?

Vérifiez trois éléments, dans cet ordre. Premièrement, le montage qui contient les médias : le script d’initialisation ne modifie le propriétaire que de /app, /config et /defaults. Ainsi, /data ou /downloads conserve le propriétaire défini sur l’hôte. Deuxièmement, le groupe partagé : si le client de téléchargement et Sonarr utilisent des GID différents, aucun des deux ne peut modifier les fichiers de l’autre. Attribuez donc le même PGID à chaque conteneur de la stack. Troisièmement, l’umask : la valeur par défaut de l’image, UMASK=022, crée les fichiers avec les permissions 0644, sans droit d’écriture pour le groupe. Cela empêche complètement l’utilisation d’un groupe partagé. Définissez UMASK=002 et activez le bit setgid sur les répertoires avec chmod 2775 afin que les nouveaux fichiers héritent du groupe.