Tutoriel

Docker volumes : corriger les permissions en 3 commandes

Déploiement10 min de lecture10 étapes

L'application démarre, le conteneur tourne, mais les logs affichent `Permission denied` et rien ne se sauvegarde. Ce problème touche la quasi-totalité des applications self-hostées dès que vous montez un répertoire hôte dans Docker. La cause est toujours la même : l'UID de l'utilisateur qui tourne à l'intérieur du conteneur ne correspond pas au propriétaire du répertoire sur l'hôte. Ce guide explique le mécanisme, donne les commandes de diagnostic et propose les corrections adaptées à chaque cas — sans jamais faire tourner vos conteneurs en root.

Sommaire· Le mécanisme : pourquoi Docker refuse d'écrire1/9
  1. 01Le mécanisme : pourquoi Docker refuse d'écrire
  2. 02Les applications les plus touchées et leur UID
  3. 03Diagnostic : identifier le problème en moins de 2 minutes
  4. 04Correction cas par cas : chown sur le répertoire hôte
  5. 05Variantes selon l'app : bind-mounts, volumes nommés et user:
  6. 06Cas NFS et montages distants
  7. 07Méthodes de correction : comparaison
  8. 08Dépannage : 4 erreurs classiques avec leur message exact
  9. 09Conclusion

Le mécanisme : pourquoi Docker refuse d'écrire

Docker partage le noyau Linux de l'hôte. Il n'existe pas de couche de virtualisation des utilisateurs : chaque processus dans un conteneur possède un UID et un GID réels, visibles depuis l'hôte. Quand un conteneur monte un répertoire hôte (bind-mount), le système de fichiers ne fait aucune magie : il applique les mêmes règles de permissions POSIX que pour n'importe quel processus local. Un fichier appartenant à l'UID 1000 sur l'hôte reste la propriété de l'UID 1000, peu importe que ce soit un utilisateur qui s'appelle alice côté hôte ou paperless côté conteneur. Si le processus dans le conteneur tourne sous l'UID 472 et que le répertoire appartient à root (UID 0), l'écriture est refusée — même si vous avez utilisé chmod 755.

Pourquoi les images n'utilisent-elles pas root par défaut ? La majorité des images Docker bien maintenues créent un utilisateur applicatif dédié pour respecter le principe de moindre privilège : un processus compromis ne peut pas modifier les binaires système du conteneur ni, si les options de sécurité sont correctes, accéder aux fichiers root de l'hôte. C'est une bonne pratique — mais elle introduit précisément ce décalage d'UID.

Le piège classique : vous créez le répertoire avec votre session SSH (UID 1000), puis vous lancez un conteneur Grafana qui tourne en UID 472. Grafana tente d'écrire sa base SQLite dans /var/lib/grafana monté depuis /opt/grafana/data — qui appartient à votre utilisateur SSH. Résultat : GF_PATHS_DATA='/var/lib/grafana' is not writable. Le conteneur démarre, répond sur le port 3000, mais toute configuration est perdue au redémarrage suivant car rien n'a jamais été écrit sur le disque.

Les applications les plus touchées et leur UID

Les problèmes de permissions Docker touchent principalement les applications qui s'exécutent avec un UID spécifique différent de celui de l'hôte.

  • Paperless-ngx — UID 1000 (utilisateur paperless) : gère les répertoires consume, export, media et data
  • Grafana — UID 472 (utilisateur grafana) : écrit sa base SQLite et ses plugins dans /var/lib/grafana
  • Nextcloud (image Debian) — UID 33 (utilisateur www-data) : répertoire de données, config et logs
  • Immich — UID 1000 (utilisateur node) : librairie photos, miniatures et base de données ML
  • Gitea — UID 1000 (utilisateur git) : dépôts, SSH keys, logs et base SQLite par défaut

Diagnostic : identifier le problème en moins de 2 minutes

Avant de corriger, confirmez que c'est bien un problème de permissions et identifiez l'UID en cause. Trois commandes suffisent.

  1. Lire le message d'erreur exact

    Consultez les logs du conteneur incriminé :

    docker logs <nom-du-conteneur> 2>&1 | grep -i 'permission\|denied\|cannot\|mkdir'

    Un message permission denied ou cannot create directory confirme le diagnostic.

  2. Identifier l'UID du processus dans le conteneur

    docker exec <nom-du-conteneur> id

    Sortie typique : uid=472(grafana) gid=0(root). Notez l'UID — ici 472.

  3. Vérifier le propriétaire du répertoire hôte

    ls -ln /opt/grafana/data

    La sortie drwxr-xr-x 2 0 0 ... indique que le répertoire appartient à root (UID 0). L'UID 472 n'a que les droits « other » — lecture et exécution, pas d'écriture.

  4. Vérifier avec stat pour un diagnostic complet

    stat /opt/grafana/data

    Regardez les lignes Uid: et Gid:. Si elles affichent (0/root) alors que votre conteneur tourne en 472, le problème est confirmé.

  5. Inspecter la configuration du conteneur

    docker inspect <nom-du-conteneur> | grep -A5 'Mounts'

    Cette commande liste tous les bind-mounts et volumes nommés actifs, avec leur source sur l'hôte et leur destination dans le conteneur.

Correction cas par cas : chown sur le répertoire hôte

La correction de base est un chown du répertoire hôte vers l'UID attendu par le conteneur. Voici les commandes pour les applications les plus courantes.

  1. Grafana (UID 472)

    mkdir -p /opt/grafana/data
    chown -R 472:472 /opt/grafana/data

    Dans votre compose.yml :

    volumes:
      - /opt/grafana/data:/var/lib/grafana
  2. Nextcloud (UID 33, image Debian)

    mkdir -p /opt/nextcloud/{data,config,apps}
    chown -R 33:33 /opt/nextcloud/data
    chown -R 33:33 /opt/nextcloud/config

    Attention : l'image Alpine utilise l'UID 82. Vérifiez avec docker exec <conteneur> id www-data si vous n'êtes pas sûr de la variante utilisée.

  3. Paperless-ngx (UID 1000)

    mkdir -p /opt/paperless/{consume,export,media,data}
    chown -R 1000:1000 /opt/paperless/consume
    chown -R 1000:1000 /opt/paperless/export
    chown -R 1000:1000 /opt/paperless/media
    chown -R 1000:1000 /opt/paperless/data
  4. Immich (UID 1000)

    mkdir -p /opt/immich/{library,thumbnails,encoded-video,profile}
    chown -R 1000:1000 /opt/immich

    Note : les variables d'environnement PUID/PGID ne fonctionnent PAS avec les images officielles Immich. Elles sont spécifiques aux images LinuxServer.io.

  5. Vérifier après correction

    ls -ln /opt/grafana/data

    Doit afficher drwxr-xr-x 2 472 472 .... Redémarrez ensuite le conteneur :

    docker compose restart grafana
    docker logs grafana --tail 20

Variantes selon l'app : bind-mounts, volumes nommés et user:

Le chown sur le répertoire hôte fonctionne pour les bind-mounts, mais Docker propose trois autres approches selon le contexte et l'image utilisée.

Directive user: dans compose.yml. Certaines images sont conçues pour accepter un UID arbitraire passé via user:. Cela évite le chown si votre répertoire appartient à votre utilisateur hôte :

services:
  app:
    image: mon-image
    user: "1000:1000"
    volumes:
      - /home/user/data:/app/data

Cette approche fonctionne uniquement si l'image ne requiert pas de fichiers internes appartenant à un UID spécifique (binaires SUID, sockets, etc.). Paperless-ngx supporte ce mode via sa variable USERMAP_UID ; Grafana ne le supporte pas proprement car son image modifie des permissions lors de son entrypoint. Si vous forcez user: 472:472 sur Grafana, le conteneur échoue au démarrage avec des erreurs sur /var/lib/grafana.

Variables d'environnement PUID/PGID. Les images de la communauté LinuxServer.io exposent les variables PUID et PGID : l'entrypoint de l'image reçoit un processus root, change dynamiquement l'UID de l'utilisateur applicatif avec usermod/groupmod, puis descend de privilèges. C'est pratique mais ces variables sont propres aux images LinuxServer.io — elles n'ont aucun effet sur les images officielles de Grafana, Nextcloud ou Immich. Ne les mélangez pas.

Volumes nommés. Avec un volume Docker nommé (docker volume create), Docker gère lui-même le répertoire sous /var/lib/docker/volumes/. À la première écriture, le répertoire est créé avec les permissions du processus du conteneur. Résultat : pas de problème de permissions au démarrage, mais la migration des données existantes nécessite une étape de copie explicite :

docker run --rm \
  -v mon-ancien-repertoire:/from \
  -v mon-volume-nomme:/to \
  alpine sh -c 'cp -a /from/. /to/'

Les volumes nommés conviennent bien aux nouvelles installations ; pour les données existantes, le bind-mount avec un chown préalable reste plus prévisible.

Cas NFS et montages distants

Les montages NFS et CIFS (partages Samba/Windows) ajoutent une couche de complexité car l'arbitrage des permissions se fait à deux endroits : le système de fichiers local ET le serveur distant. Comprendre lequel des deux bloque est essentiel avant de chercher une correction.

Problème NFS — root_squash. Par défaut, un serveur NFS exporte avec root_squash : tout accès qui arrive avec UID 0 (root) est automatiquement réattribué à l'utilisateur nobody (UID 65534). Si votre conteneur tourne en UID 472 et que le partage NFS n'a pas de mapping UID côté serveur, le client monte le partage, voit les fichiers, mais l'écriture est refusée — parce que le serveur NFS considère que l'UID 472 du client n'a pas les droits sur ce partage.

Solutions :
1. Configurer l'export NFS avec all_squash,anonuid=472,anongid=472 pour Grafana, ou l'UID correspondant à votre application.
2. Créer un utilisateur côté serveur NFS avec le même UID que dans le conteneur et lui attribuer la propriété des répertoires partagés.
3. Utiliser no_root_squash uniquement si vous contrôlez complètement le réseau interne — cette option permet à root sur le client d'agir comme root sur le serveur, ce qui est un risque de sécurité significatif.

Problème CIFS — options de montage figées. Contrairement à ext4 ou XFS, un montage CIFS ne supporte pas le chown après montage : la propriété est fixée par les options passées à la commande de montage. Si vous montez un partage Windows sans spécifier d'UID, les fichiers apparaissent en root (UID 0) et les conteneurs Nextcloud (UID 33) ne peuvent pas y écrire, quelle que soit la permission POSIX visible côté client.

Pour les montages CIFS dans /etc/fstab :

//serveur/partage /opt/nextcloud/data cifs uid=33,gid=33,credentials=/etc/cifs-creds,iocharset=utf8 0 0

Vérifiez toujours que le fichier de credentials (/etc/cifs-creds) n'est lisible que par root (chmod 600).

Méthodes de correction : comparaison

Faites défiler le tableau

MéthodeAvantagesRisques / Limites
chown UID:GID sur le répertoire hôteSimple, universelle, compatible toutes imagesNécessite de connaître l'UID exact ; à refaire si le répertoire est recréé
user: UID:GID dans compose.ymlPas de chown à gérer ; portable entre hôtesL'image doit supporter les UIDs arbitraires ; peut casser des fichiers internes
Volumes nommés DockerPermissions gérées automatiquement au premier démarrageMoins lisible ; migration de données existantes plus complexe
--privileged ou chmod 777Résout le problème immédiatementDANGER : expose l'hôte et tous ses processus ; ne jamais utiliser en production

Dépannage : 4 erreurs classiques avec leur message exact

Voici les quatre erreurs Docker les plus courantes liées aux permissions, avec le message d'erreur exact et sa solution.

  • mkdir: cannot create directory '/var/lib/grafana/plugins': Permission denied → Le répertoire hôte n'appartient pas à l'UID 472. Appliquez chown -R 472:472 /opt/grafana/data.
  • [Errno 13] Permission denied: '/usr/src/paperless/media' → Paperless-ngx ne peut pas écrire dans son répertoire media. Vérifiez que le bind-mount appartient à l'UID 1000 sur l'hôte.
  • Could not create lock file /var/lib/grafana/.~lock.grafana.db → Grafana peut lire le répertoire mais pas y écrire. Problème de permissions sur les fichiers existants : chown 472:472 /opt/grafana/data/*.db ou supprimez le lock file.
  • chown: changing ownership of '/data': Operation not permitted (au démarrage du conteneur) → L'image tente elle-même de corriger les permissions mais ne peut pas car elle ne tourne pas en root. Effectuez le chown manuellement sur l'hôte AVANT de démarrer le conteneur.

SELinux et AppArmor : les flags :z et :Z dans les bind-mounts. Sur les systèmes avec SELinux actif (CentOS, RHEL, Fedora), un bind-mount peut être bloqué même si les permissions POSIX sont correctes. Docker fournit deux suffixes : :z relabellise le contenu pour le partager entre plusieurs conteneurs, et :Z relabellise en accès privé (un seul conteneur). Exemple : - /opt/grafana/data:/var/lib/grafana:z. Sans ce flag sur un système SELinux, vous obtiendrez Permission denied même après un chown correct. Pour vérifier si SELinux bloque : ausearch -m AVC -ts recent | grep docker.

Conclusion

Le Permission denied dans les logs Docker n'est jamais une fatalité. La solution tient en trois commandes : docker exec <conteneur> id pour connaître l'UID, ls -ln <répertoire-hôte> pour confirmer le propriétaire actuel, puis chown -R <UID>:<GID> <répertoire-hôte> pour corriger. La règle d'or : créer les répertoires hôtes avec le bon propriétaire avant de démarrer le conteneur, pas après.

Pour éviter de reproduire le problème à chaque nouvelle installation, documentez les UIDs dans votre compose.yml sous forme de commentaires et intégrez le chown dans votre script de provisionnement ou votre playbook Ansible. Un rôle Ansible qui crée les répertoires de données avec owner: 472 et group: 472 (pour Grafana) avant le docker compose up élimine la cause à la racine.

Sur un VPS dédié, cette discipline évite 90 % des incidents de données silencieusement perdues au redémarrage — des incidents qui ne produisent aucune alerte car le conteneur est vert, le service répond, mais tout ce qui a été configuré depuis le dernier démarrage a disparu. Un chown de deux secondes avant le premier lancement vous en épargne des heures de débogage.

Un VPS prêt pour le self-hosting

Déployez vos applications Docker sur un VPS ServOrbit avec accès root, IPv4 dédiée et snapshots automatiques. À partir de 99 DH/mois.

Besoin d'aide ?

Parcourez notre centre d'aide et notre FAQ, ou contactez notre équipe — rappel, WhatsApp ou e-mail. Support en français, anglais et arabe.

Écrire sur WhatsApps'ouvre dans un nouvel onglet