SSD Nodes Learn Hosting plans →
Guides Matt ConnorPar Matt Connor · Mis à jour le 2026-09-23

À quoi servent PUID et PGID dans Docker Compose ?

PUID et PGID ne sont pas des paramètres Docker : dans les images linuxserver.io, ils évitent les fichiers de bind mount en 911:911. Voici comment corriger cela.

Ce que sont réellement PUID et PGID

PUID et PGID sont deux variables d’environnement que certaines images de conteneurs 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 nommé abc est créé lors du build avec l’UID (user ID) 911 et le GID (group ID) 911. Le conteneur démarre avec root, exécute ses scripts d’initialisation, puis l’un de ces scripts modifie les identifiants de cet utilisateur avant toute autre opération :

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

Le flag -o permet d’utiliser un identifiant déjà utilisé ailleurs. Ensuite, le script d’initialisation abandonne les privilèges et lance l’application avec l’utilisateur abc. Ainsi, PUID=1000 n’est jamais transmis à Docker. La variable modifie l’identifiant d’un utilisateur à l’intérieur du conteneur avant le démarrage de l’application. Tous les fichiers écrits par l’application sur votre disque appartiennent donc à l’UID 1000. Si vous ne définissez pas PUID, abc conserve l’UID 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 le compte qui possède les 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. Après une reconstruction du serveur ou l’ajout ultérieur d’un deuxième compte, la valeur peut être 1001 ou plus. Une valeur incorrecte 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 sous 911:911

ls -l affiche un identifiant numérique au lieu d’un nom lorsqu’aucun compte de l’hôte ne correspond à cet identifiant. Aucun compte de votre serveur n’a l’UID 911. Il n’y a donc aucun nom à afficher. Utilisez ls -ln pour afficher systématiquement les numéros et supprimer 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 paramètres intégrés par défaut. Le fichier config.xml affiché dans cette liste contient également les paramètres d’authentification. C’est important lors de la première ouverture de l’interface web, lorsque vous constatez que Sonarr et Radarr sont livrés sans nom d’utilisateur ni mot de passe par défaut. Vérifiez-le depuis l’intérieur du conteneur au lieu de le supposer :

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

L’init linuxserver affiche le résultat dans le journal de démarrage, sur 2 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’est jamais parvenue jusqu’au conteneur. La cause habituelle est que vous avez modifié docker-compose.yml, puis exécuté docker compose restart, qui réutilise le conteneur existant avec son environnement initial. Les modifications de l’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 kernel 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, vous devez disposer de la permission d’écriture sur son répertoire, pas sur le fichier lui-même. Vous obtenez donc cette erreur même lorsque le fichier 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 et l’application le signale avec ses propres termes. Dans une application .NET comme 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 vous indique lequel des trois ensembles de permissions s’applique réellement à votre utilisateur. Lire correctement drwxr-xr-x permet de comprendre immédiatement cette erreur.

Il s’agit spécifiquement 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 pas de ce traitement : Docker monte le 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

Définir PUID et PGID modifie le comportement de l’application à partir de maintenant. Cela 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. Faites-le lorsque le conteneur est arrêté, car 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 à comprendre.

Ce que PUID et PGID ne corrigent pas

Voici le point qui piège les personnes qui ont pourtant tout configuré correctement. Au démarrage, l’init de linuxserver exécute un chown sur exactement trois chemins : /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 normalement, 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 sur une bibliothèque de médias de douze téraoctets à chaque démarrage du conteneur serait désastreux. Les répertoires de médias sont donc sous votre responsabilité. Ce sont aussi les montages où les permissions posent réellement problème. Ce type d’échec apparaît discrètement dans le journal de l’application, plusieurs heures après que le conteneur a semblé sain. Un test d’écriture périodique relié à ntfy sur votre propre VPS, avec des alertes envoyées sur votre téléphone est un moyen simple d’être averti avant qu’une semaine d’épisodes manquants ne révèle le problème.

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 lit ces variables. Elle est courante parce que le conteneur démarre toujours en tant que root, effectue sa configuration, corrige /config, puis abandonne les privilèges. Les Docker Mods et les scripts d’initialisation personnalisés continuent de fonctionner. En contrepartie, vous dépendez d’une convention et non d’une fonctionnalité de la plateforme, et les noms des variables ne sont pas standardisés entre les projets.

La clé user: dans Compose

Il s’agit d’une véritable fonctionnalité Docker. Elle 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 véritable 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 en charge cette configuration 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, les Docker Mods ne s’exécutent pas, les services personnalisés ne s’exécutent pas et vous devez gérer les permissions de chaque volume monté. Le schéma 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 secondaire purement esthétique surprend certains utilisateurs. Un user: numérique ne possède aucune entrée correspondante dans le /etc/passwd du conteneur. Les outils à l’intérieur du conteneur affichent 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é. Aucun processus sur la machine ne s’exécute donc en tant que root réel. Le calcul des propriétaires change complètement. L’UID 0 du conteneur correspond à l’UID de l’utilisateur hôte qui exécute Docker rootless. L’UID n du conteneur, pour toute n égale ou supérieure à 1, correspond à subuid + (n - 1), où subuid est le début de la plage qui vous est attribuée dans /etc/subuid et /etc/subgid. Docker exige au moins 65,536 IDs secondaires dans ce fichier.

Relisez cette correspondance, car elle 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 proche de 100999, auxquels votre shell ne peut pas accéder. La valeur PUID correcte avec un daemon rootful est donc incorrecte ici. Ces deux mécanismes résolvent le même problème à des niveaux différents. Les empiler sans vérifier leur fonctionnement est la meilleure façon de se retrouver avec un répertoire qu’il faut sudo pour supprimer. Si vous passez à rootless, testez le propriétaire 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 sont le choix pragmatique, car c’est ce pour quoi les images sont conçues et documentées. Utilisez user: lorsque le README de l’image indique qu’elle est testée dans ce mode, ou lorsque vous utilisez une image officielle upstream qui ne prend pas du tout en charge PUID. Un espace de travail documentaire tel que une instance AFFiNE auto-hébergée sur un VPS relève du second cas, car aucun de ses conteneurs ne lit PUID. Les droits sur son répertoire de base de données et ses fichiers téléversés sont alors déterminés par le runtime, et non par une variable du bloc d’environnement. Il en va de même pour un centre de support Chatwoot auto-hébergé : le conteneur Rails et son worker Sidekiq écrivent tous deux dans un même répertoire de fichiers téléversés, et aucun des deux ne lit PUID. Ce répertoire doit donc correspondre à l’utilisateur sous lequel l’image s’exécute déjà. Rien ne change avec une stack plus récente. Ainsi, donner à chaque membre de l’équipe son propre agent OneCLI isolé laisse les répertoires de travail individuels et le répertoire de données Postgres appartenir à l’utilisateur sous lequel chaque image s’exécute déjà. Il s’agit donc d’un problème de user: et de chown, et non d’un problème lié à PUID. Si vous placez une seule API auto-hébergée devant Codex, Claude Code et Hermes, vous conservez la même organisation. Cette image s’exécute avec son propre utilisateur intégré, et le bind mount qui contient sa base de données et ses clés stockées reprend les droits associés à cet utilisateur.

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

Une stack media arr avec Sonarr, Radarr et un client de téléchargement permet de vérifier que ce principe fonctionne en pratique. 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 de répertoires. Si le client de téléchargement s’exécute avec l’UID 1000 et Sonarr avec l’UID 1001, l’un des deux possède des fichiers que l’autre peut seulement lire.

La solution consiste à utiliser un groupe partagé comme PGID par tous les conteneurs de la stack :

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 placé au début de 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 du groupe primaire 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 vos propres 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 utilise ce même PGID. Cela concerne aussi les conteneurs situés plus loin dans la chaîne, qui lisent uniquement la bibliothèque terminée, comme Jellyfin lui-même et les interfaces que vous lui ajoutez, par exemple Halcyon, qui présente cette bibliothèque comme un vidéoclub des années 90 dans lequel on peut parcourir les titres.

Définissez ensuite UMASK=002 sur 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 supprime le bit d’écriture du groupe pour chaque nouveau fichier. Les fichiers sont donc créés avec 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. Toute la stack utilise ainsi une seule définition :

PUID=1000
PGID=13000

Compose lit automatiquement ce fichier pour effectuer la substitution de variables au format ${PUID}. C’est le même mécanisme que celui utilisé pour les identifiants. Les bonnes pratiques consistant à conserver les valeurs hors de docker-compose.yml dans un fichier .env s’appliquent également ici, à la différence que ces deux nombres ne sont pas des 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 indique 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 idée : 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. Les serveurs photo présentent la même diversité : PhotoPrism possède sa propre paire PHOTOPRISM_UID et PHOTOPRISM_GID, tandis qu’Immich ne fournit aucun équivalent et laisse l’utilisateur du conteneur utiliser la clé Docker user:. Ainsi, choisir entre PhotoPrism et Immich détermine aussi le mécanisme que vous devrez maintenir pour la bibliothèque la plus importante du serveur. De nombreuses images officielles upstream, 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 tel quel. Les petits déploiements mono-application posent la même question. Lorsque vous mettez en place un outil de suivi d’entraînement openGym auto-hébergé, vérifiez donc sous quel utilisateur le conteneur s’exécute réellement avant de lui associer un bind mount. Le répertoire qui contient sa base de données dépend de cette configuration, que vous définissiez ou non PUID. Un relais d’accès distant relève de la même catégorie. Lorsque vous exécutez votre propre serveur relais RustDesk, la paire de clés Ed25519 que hbbs écrit au premier démarrage arrive dans votre bind mount avec comme propriétaire l’utilisateur utilisé par cette image. Un chown côté hôte est alors la seule correction dont vous disposez. Il en va de même pour les composants d’infrastructure que vous ajoutez par la suite. Ainsi, placer Authentik devant vos applications pour centraliser l’authentification implique d’exécuter les images officielles du serveur, de Postgres et de Redis. Ces images ne lisent aucune variable PUID. La propriété de leurs volumes dépend donc du runtime, et non d’un entrypoint que vous pouvez configurer.

Consultez donc le README de chaque image avant de copier un bloc d’environnement d’un projet à un autre. Docker transmet toute variable d’environnement que vous définissez à n’importe quel conteneur, qu’un composant interne la lise ou non. Un PUID qui n’est consommé par aucun composant ne produit ni erreur, ni avertissement, ni effet. Le conteneur s’exécute avec l’utilisateur défini par son propre Dockerfile. Vous pouvez le déterminer en observant la propriété des fichiers qu’il crée. Effectuez cette vérification avant d’ajouter quoi que ce soit au serveur, notamment une stack d’analyse de sécurité open-kritt auto-hébergée. Son fichier Compose indique si les images prennent en charge PUID ou si la propriété des répertoires montés est fixée par les images elles-mêmes.

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 signifie 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 ne possède l’ID 911. Il n’existe donc aucun nom à afficher. 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 sur 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 fonctionnement. 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 et 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, car l’entrypoint continue de s’exécuter en tant que root assez longtemps 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, définir user: rend PUID et PGID inopérants, 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 l’ordre. Premièrement, le montage contenant les médias : le script d’initialisation ne modifie le propriétaire que de /app, /config et /defaults. Ainsi, /data ou /downloads conserve les permissions définies 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 des fichiers avec les permissions 0644, sans droit d’écriture pour le groupe. Cela rend le groupe partagé inutilisable. 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.