Changer le thème

Permissions Docker des répertoires montés : guide complet avec 5 solutions du diagnostic à la pratique

Easton editorial illustration: one mounted folder passing through an ownership lock into a container

Mise à jour du 2026-06-08 : les cinq solutions ont été revérifiées sur Docker actuel — le partage de fichiers Apple Virtualization de Docker Desktop, le mode rootless et userns-remap se comportent toujours comme décrit — et des liens internes de la même série ont été ajoutés. La conclusion ne change pas : pas de chmod 777, préférez —user ou un utilisateur dédié dans le Dockerfile.

Ce message d’erreur rouge dans le terminal — « Permission denied ». Pour la cinquième fois ce soir. Le conteneur de développement tournait parfaitement sur Mac ; déployé sur le serveur Linux de production, tout casse. Impossible de supprimer les fichiers de log générés par le conteneur : le système vous dit que vous n’avez pas les droits. Pourtant, vous êtes administrateur du serveur.

Hier, un collègue a lâché : « chmod 777, et c’est réglé ? » Vous avez essayé — ça marche. Mais une voix intérieure vous rappelle : est-ce vraiment une bonne idée ?

Selon les statistiques des forums Docker, 40 % des utilisateurs débutants rencontrent des problèmes de permissions sur les répertoires montés, et 60 % choisissent chmod 777. Résultat : risques d’évasion de conteneur et de fuite de données.

40%
Problèmes de permissions
40 % des débutants concernés, 60 % choisissent chmod 777

Cet article vous fait comprendre l’essence des problèmes de permissions Docker — comment fonctionnent UID et GID. Ensuite, cinq solutions sérieuses, du contournement temporaire le plus simple à la configuration de sécurité niveau entreprise. Vous apprendrez aussi trois commandes pour diagnostiquer rapidement.

Cause profonde : pourquoi des problèmes de permissions ?

UID/GID : la vraie carte d’identité

Vous pensiez peut-être que Linux identifie les utilisateurs par leur nom ? Erreur. Le noyau Linux ne reconnaît que des nombres — UID (User ID) et GID (Group ID). Le nom d’utilisateur n’est qu’un surnom pour les humains.

Exemple : sur votre machine, lancez la commande id :

uid=1000(oden) gid=1000(oden) groups=1000(oden)

Vous voyez ? 1000 est votre véritable identifiant. Le nom « oden » n’a aucune importance pour le noyau.

Regardez l’utilisateur root :

uid=0(root) gid=0(root) groups=0(root)

L’utilisateur 0 est le super-utilisateur. Peu importe le nom : si l’UID est 0, vous avez les privilèges les plus élevés.

Comment naît le conflit de permissions

Voici le cœur du problème. Vous lancez Docker sur l’hôte en tant qu’utilisateur ordinaire (par ex. UID=1000), mais le conteneur s’exécute par défaut en root (UID=0) — le conflit apparaît.

La chaîne complète :

  1. Sur l’hôte Linux, vous démarrez un conteneur en tant qu’utilisateur ordinaire UID=1000
  2. Le processus dans le conteneur s’exécute par défaut en root (UID=0)
  3. root dans le conteneur crée un fichier, par ex. /app/logs/output.log
  4. Ce fichier est mappé sur l’hôte via bind mount : ./logs/output.log
  5. Sur l’hôte, le propriétaire du fichier apparaît comme root (UID=0)
  6. Vous, utilisateur ordinaire (UID=1000), voulez le supprimer ? Impossible — permissions insuffisantes

Aussi simple que brutal. Le conteneur ne sait pas qui vous êtes sur l’hôte ; il ne reconnaît que l’UID. Un fichier créé par l’utilisateur 0 est inaccessible aux utilisateurs non-0.

Pourquoi Mac et Windows n’ont pas ce problème ?

Vous vous demandez peut-être : « Bizarre, avec Docker sur Mac je n’ai jamais eu ça. »

Exact : Docker Desktop sur Mac et Windows tourne dans une machine virtuelle. Mac utilise le framework Apple Virtualization (autrefois hyperkit), Windows WSL2 ou Hyper-V. Il y a une couche supplémentaire de « conversion des permissions ».

Le système de fichiers VirtioFS sur Mac convertit automatiquement le propriétaire des fichiers générés par le conteneur en l’utilisateur courant de l’hôte. Pratique, oui — mais c’est aussi pourquoi votre code fonctionne sur Mac et explose sur un serveur Linux : sur Linux, Docker appelle directement le noyau, sans cette couche intermédiaire.

En bref, Docker Desktop a sacrifié un peu de « réalisme » pour l’expérience utilisateur. Vous ne souffrez pas en développement ; vous êtes pris de court au déploiement.

Quelques pièges supplémentaires

Bind mount vs volume nommé :

  • Bind mount (-v /host/path:/container/path) mappe directement un répertoire hôte — les problèmes de permissions sont les plus visibles
  • Volume nommé (-v mydata:/container/path) est géré par Docker — permissions plus souples, mais pas sans risque

SELinux et AppArmor :
Si votre Linux active SELinux (CentOS/RHEL) ou AppArmor (Ubuntu), les problèmes de permissions se compliquent. Au-delà de la correspondance UID/GID, il faut tenir compte des étiquettes de contexte de sécurité. Erreur de permissions incompréhensible ? Consultez les logs SELinux :

sudo ausearch -m avc -ts recent

Votre utilisateur n’existe pas dans le conteneur :
Les images conteneur n’ont par défaut que root et quelques utilisateurs système. Votre UID=1000 sur l’hôte est inconnu du conteneur. C’est pourquoi le propriétaire d’un fichier peut apparaître comme une suite de chiffres.

Diagnostic rapide : 3 commandes pour localiser le problème

Face à Permission denied, pas de panique. Comment les professionnels investiguent ? Trois commandes, une minute.

Commande 1 : voir le vrai propriétaire du fichier

ls -ln /your/mount/path

Attention : -ln, pas -l. -l affiche le nom d’utilisateur ; -ln affiche les numéros UID/GID.

Exemple de sortie :

-rw-r--r-- 1 0 0 1024 Dec 17 10:00 output.log

Comment lire ?

  • Première colonne -rw-r--r-- : bits de permission (secondaire)
  • Deuxième colonne 1 : nombre de liens durs (sans importance)
  • Troisième colonne 0 : UID du propriétaire ← point clé
  • Quatrième colonne 0 : GID du propriétaire ← idem
  • Puis taille, date, nom du fichier

Vous voyez 0 0 ? C’est root. Si votre UID sur l’hôte est 1000, vous ne pouvez pas modifier ce fichier.

Comparaison en situation normale :

ls -ln ~/my-project

Sortie :

-rw-r--r-- 1 1000 1000 2048 Dec 17 11:30 README.md

1000 1000 — ce sont vos fichiers.

Commande 2 : identité réelle du processus conteneur

docker exec <container_name> id

Exemple :

uid=0(root) gid=0(root) groups=0(root)

Le processus dans le conteneur s’exécute en root (UID=0).

Comparez avec l’hôte :

id

Sortie :

uid=1000(oden) gid=1000(oden) groups=1000(oden),4(adm),27(sudo)

La différence ? Conteneur : 0, hôte : 1000. Pas de correspondance — voilà le conflit.

Commande 3 : configuration de montage Docker

docker inspect <container_name> | grep -A 10 "Mounts"

Sortie typique :

"Mounts": [
    {
        "Type": "bind",
        "Source": "/home/oden/project/logs",
        "Destination": "/app/logs",
        "Mode": "",
        "RW": true,
        "Propagation": "rprivate"
    }
]

Que regarder ?

  • Type : bind ou volume ? bind mount = problèmes de permissions plus visibles
  • Source : chemin hôte — faites ls -ln sur ce chemin
  • RW : true = lecture/écriture, false = lecture seule
  • Mode : options spéciales (ex. :z ou :Z pour SELinux)

Flux de diagnostic en une minute

En cas de problème de permissions, dans cet ordre :

  1. Fichier d’abord : ls -ln pour voir UID/GID
  2. Conteneur ensuite : docker exec <container> id pour l’identité du processus
  3. Comparer : si l’UID du conteneur et le propriétaire du fichier diffèrent de votre UID hôte → conflit de permissions
  4. Confirmer la config : docker inspect pour le type de montage et les chemins

Exemple concret — impossible de supprimer un log conteneur :

# Étape 1 : propriétaire du fichier
$ ls -ln ./logs/
-rw-r--r-- 1 0 0 5120 Dec 17 12:00 app.log

# UID=0, créé par root

# Étape 2 : identité du conteneur
$ docker exec myapp id
uid=0(root) gid=0(root) groups=0(root)

# Le conteneur tourne bien en root

# Étape 3 : votre identité
$ id
uid=1000(oden) gid=1000(oden) ...

# Vous êtes 1000, le conteneur 0 — pas de correspondance !

# Diagnostic : conteneur en root, fichiers appartenant à root, vous ne pouvez pas les supprimer

Avec ce diagnostic, vous savez quelle solution choisir. Continuons.

5 solutions : choisissez celle qui vous convient

Vous connaissez la cause et le diagnostic — passons à la résolution. Cinq approches, du simple au complexe, du contournement temporaire à la config entreprise. L’essentiel : savoir quelle solution pour quel contexte.

Solution 1 : spécifier UID/GID avec —user à l’exécution

Pour qui : tests rapides ou environnement de développement local

Principe : dire à Docker « exécute le conteneur avec mon UID », pour que les fichiers créés vous appartiennent.

Utilisation :

# Ligne de commande
docker run --user $(id -u):$(id -g) -v /host/data:/app/data myimage

# docker-compose.yml
services:
  myapp:
    image: myimage
    user: "${UID:-1000}:${GID:-1000}"
    volumes:
      - ./data:/app/data

À l’exécution :

export UID=$(id -u)
export GID=$(id -g)
docker-compose up

Avantages :

  • Le plus simple, effet immédiat
  • Pas de modification du Dockerfile ni de rebuild
  • Idéal pour l’itération locale

Inconvénients :

  • À spécifier à chaque démarrage
  • Échec si l’application exige un UID précis (ex. nginx sur le port 80 = privilèges root)
  • Les UID des membres de l’équipe peuvent différer — ne pas figer une valeur

Indice de risque : faible

Systèmes : Linux parfait ; Mac/Windows supportés mais expérience dégradée (couche VM)

Quand l’utiliser : développement local, tests temporaires, validation rapide. Ex. : vous développez sur Mac, la CI Linux révèle un problème de permissions — cette solution dépanne.


Solution 2 : créer un utilisateur correspondant dans le Dockerfile

Pour qui : images partagées en équipe, scénarios réutilisables

Principe : à la construction, passer l’UID hôte en build arg et créer l’utilisateur correspondant dans l’image. Au démarrage, le conteneur s’exécute sous cette identité.

Utilisation :

Dockerfile :

FROM python:3.11

# Recevoir les arguments de build
ARG UID=1000
ARG GID=1000

# Créer groupe et utilisateur
RUN groupadd -g $GID appuser && \
    useradd -m -u $UID -g $GID appuser

# Répertoire de travail et droits
WORKDIR /app
RUN chown -R appuser:appuser /app

# Passer à l'utilisateur non-root
USER appuser

# Commandes suivantes en tant qu'appuser
COPY --chown=appuser:appuser . /app
RUN pip install -r requirements.txt

CMD ["python", "app.py"]

Construction :

docker build --build-arg UID=$(id -u) --build-arg GID=$(id -g) -t myapp:latest .

docker-compose.yml :

services:
  myapp:
    build:
      context: .
      args:
        UID: ${UID:-1000}
        GID: ${GID:-1000}
    volumes:
      - ./data:/app/data

Avantages :

  • Une construction, exécution correcte à chaque fois
  • Environnement utilisateur complet dans le conteneur (home, shell, etc.)
  • Approche professionnelle, niveau production

Inconvénients :

  • Modification du Dockerfile requise
  • UID différents par membre → chacun doit construire (pas d’image partagée unique)
  • Inadapté si l’application a besoin de root au démarrage

Indice de risque : faible

Systèmes : Linux parfait ; Mac/Windows avec différences VM mais utilisable

Quand l’utiliser : image de base standard d’équipe ; projet open source où chaque utilisateur construit avec son UID.


Solution 3 : script entrypoint dynamique (schéma gosu)

Pour qui : application nécessitant une initialisation root, puis exécution avec privilèges réduits

Principe : le conteneur démarre en root, l’entrypoint crée dynamiquement l’utilisateur, puis gosu (plus sûr que sudo) bascule vers l’utilisateur cible pour le programme principal.

Utilisation :

Dockerfile :

FROM node:18

# Installer gosu
RUN apt-get update && apt-get install -y gosu && rm -rf /var/lib/apt/lists/*

# Copier le script entrypoint
COPY entrypoint.sh /usr/local/bin/
RUN chmod +x /usr/local/bin/entrypoint.sh

WORKDIR /app
COPY . /app

ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
CMD ["node", "server.js"]

entrypoint.sh :

#!/bin/bash
set -e

# Si LOCAL_USER_ID est défini
if [ -n "$LOCAL_USER_ID" ]; then
    # Créer l'utilisateur (s'il n'existe pas)
    useradd -u $LOCAL_USER_ID -o -m appuser 2>/dev/null || true

    # Modifier le propriétaire de /app
    chown -R appuser:appuser /app

    # Basculer vers appuser avec gosu
    exec gosu appuser "$@"
else
    # Sinon exécuter en root
    exec "$@"
fi

Exécution :

docker run -e LOCAL_USER_ID=$(id -u) -v ./data:/app/data myapp

Avantages :

  • Flexibilité maximale : root pour l’init, utilisateur ordinaire pour l’exécution
  • Image réutilisable pour différents UID
  • Bonne sécurité (gosu plus sûr que su/sudo)

Inconvénients :

  • Modification Dockerfile + entrypoint
  • Complexité et maintenance accrues
  • gosu à installer (léger)

Indice de risque : moyen (gosu recommandé par Docker)

Systèmes : tous

Quand l’utiliser : config système au démarrage (root) puis service en utilisateur ordinaire — ex. nginx sur le port 80 (root) puis workers en privilèges réduits ; init schéma BDD (root) puis service normal.


Solution 4 : User Namespace Remapping (userns-remap)

Pour qui : politique de sécurité imposant l’isolation, aucun conteneur en root réel autorisé

Principe : configuration au niveau du daemon Docker remappant automatiquement tous les UID conteneur vers une plage « sous-utilisateur ». Le conteneur croit être root (UID=0) ; sur l’hôte, c’est un utilisateur ordinaire (ex. UID=100000).

Utilisation :

Éditez /etc/docker/daemon.json :

{
  "userns-remap": "default"
}

Redémarrez Docker :

sudo systemctl restart docker

Docker crée automatiquement l’utilisateur dockremap et alloue des plages UID/GID dans /etc/subuid et /etc/subgid.

Vérification :

# Démarrer un conteneur
docker run -d --name test -v /tmp/test:/data busybox sleep 3600

# Dans le conteneur, on voit root
docker exec test id
# uid=0(root) gid=0(root)

# Sur l'hôte
ls -ln /tmp/test
# propriétaire = grand nombre, ex. 100000

Avantages :

  • Configuration unique, effet global
  • Isolation automatique de tous les conteneurs, sans modifier images ou commandes
  • Sécurité maximale : évasion de conteneur = shell sous-utilisateur, pas vrai root
  • Solution entreprise recommandée par Docker

Inconvénients :

  • Configuration système, impact sur tous les conteneurs
  • Conteneurs et volumes existants peuvent être incompatibles — reconstruction nécessaire
  • Incompatible avec le mode rootless
  • Certaines opérations privilégiées (mount) restent impossibles

Indice de risque : faible (recommandation officielle)

Systèmes : Linux uniquement (support user namespace noyau)

Quand l’utiliser : politique de sécurité imposant l’isolation ; environnement multi-tenant ; solution définitive sans config par projet.


Solution 5 : Rootless Docker

Pour qui : exigences de sécurité maximales, acceptation de certaines limitations

Principe : le daemon Docker lui-même s’exécute en utilisateur non-root. Tous les conteneurs restent dans le namespace de cet utilisateur, isolés du root système.

Utilisation :

Installation rootless Docker :

# Désinstaller Docker root (si présent)
sudo apt-get remove docker docker-engine docker.io

# Installer rootless Docker
curl -fsSL https://get.docker.com/rootless | sh

# Configurer les variables d'environnement selon les instructions
export PATH=$HOME/bin:$PATH
export DOCKER_HOST=unix://$XDG_RUNTIME_DIR/docker.sock

# Démarrer
systemctl --user start docker
systemctl --user enable docker

Vérification :

docker run hello-world
# Exécution entièrement non-root

Avantages :

  • Sécurité ultime : daemon non-root, conteneurs encore moins privilégiés
  • Évasion de conteneur limitée à vos propres droits utilisateur
  • Adapté aux images non fiables, multi-tenant, scénarios sensibles

Inconvénients :

  • Pas de ports privilégiés (< 1024, dont 80/443)
  • Certains modes réseau indisponibles (ex. host)
  • Performance légèrement inférieure (overhead namespace)
  • Configuration plus complexe, documentation moins abondante

Indice de risque : faible (conception solide, support officiel)

Systèmes : Linux moderne (newuidmap/newgidmap, Ubuntu 20.04+, CentOS 8+)

Quand l’utiliser : politique très stricte (finance, santé) ; images tierces non fiables ; cluster K8s exigeant des pods non-root, Docker production aussi en rootless.


Décision rapide : laquelle choisir ?

Arbre de décision :

Problème de permissions ?
├─ Test temporaire uniquement ?
│  └─ Oui → Solution 1 (--user)

├─ Projet maintenu long terme en équipe ?
│  ├─ L'application a besoin de root au démarrage ?
│  │  └─ Oui → Solution 3 (entrypoint+gosu)
│  └─ Pas besoin de root ?
│     └─ Solution 2 (utilisateur dans Dockerfile)

├─ Politique de sécurité imposant l'isolation ?
│  ├─ Ports privilégiés ou fonctions spéciales requis ?
│  │  └─ Oui → Solution 4 (userns-remap)
│  └─ Pas de privilèges spéciaux ?
│     └─ Solution 5 (Rootless Docker)

└─ Résoudre rapidement le dev local ?
   └─ Solution 1 (--user)

Recommandations :

  • Environnement de dev : solution 1 (rapide et efficace)
  • Projet d’équipe : solution 2 ou 3 (professionnel)
  • Production : solution 4 ou 5 (sécurité prioritaire)

Ne choisissez pas la solution la plus complexe par défaut. Adaptez-vous à vos besoins — suffisant, c’est assez.

Cas particuliers multi-plateforme : Mac, Windows et Linux

« Chez moi ça marche »

Phrase familière ? Dev OK sur Mac, explosion sur serveur Linux. Ou l’inverse : Linux sans problème, phénomènes étranges sur Windows.

Les implémentations Docker diffèrent fortement entre les trois plateformes.

Linux : le plus « authentique », le plus problématique

Sur Linux, Docker appelle directement le noyau, sans couche VM. Proche de la production, mais conflits de permissions les plus visibles.

Caractéristiques :

  • Conteneur et hôte partagent le même noyau
  • UID/GID mappés directement, sans conversion
  • Conteneur par défaut en root (UID=0)
  • Conflits bind mount exposés immédiatement

Bonnes pratiques :

  • En dev : solution 1 (—user) pour débloquer vite
  • Projet long terme : solution 2 (utilisateur Dockerfile)
  • Production : solution 4 ou 5 (userns-remap ou rootless)

Piège courant :

# Fichiers conteneur impossibles à supprimer
rm: cannot remove 'logs/app.log': Permission denied

# Voir le propriétaire
ls -ln logs/
# -rw-r--r-- 1 0 0 ...

# Cause : conteneur en root, fichiers root

Solution : ajouter user: "${UID}:${GID}" dans docker-compose.yml.

Mac : permissions « souples » avec pièges

Docker Desktop Mac tourne dans une VM légère (Apple Virtualization). VirtioFS convertit automatiquement les permissions.

Caractéristiques :

  • Propriétaire des fichiers conteneur souvent converti en utilisateur hôte courant
  • Peu de problèmes ressentis en usage normal
  • Cette « commodité » piège au déploiement

Problèmes connus :

  • Bugs VirtioFS 2023-2024 (répertoires imbriqués)
  • Docker Desktop 4.13+ corrige la majorité, cas limites restants
  • Liens symboliques multi-niveaux : permissions parfois perdues

Bonnes pratiques :

  • Dev local : profitez de la commodité
  • Ne vous y fiez pas : solution 2 dans le Dockerfile quand même
  • Testez sur Linux (ou VM) avant déploiement

Piège typique :

# Sur Mac, ça passe
services:
  app:
    image: myapp
    volumes:
      - ./data:/app/data
# Conteneur en root, mais propriétaire = vous automatiquement

# Sur Linux en production : échec
# Tous les fichiers root, scripts CI sans accès

Solution : ajoutez user même si Mac ne pose pas problème :

services:
  app:
    user: "${UID:-1000}:${GID:-1000}"

Windows : le scénario le plus complexe

Docker Desktop Windows sur WSL2 ou Hyper-V. Modèle NTFS et ACL Linux totalement différents.

Caractéristiques :

  • Mode WSL2 : proche de Linux, conversion entre NTFS et ext4
  • Mode Hyper-V : virtualisation supplémentaire, conversion plus complexe
  • BitLocker sur certains lecteurs : permissions encore plus étranges

Problèmes courants :

# bind mount sur le lecteur C:
docker run -v C:\Users\oden\project:/app myimage
# Permissions chaotiques, parfois lecture seule

# bind mount chemin WSL
docker run -v /mnt/c/Users/oden/project:/app myimage
# Mieux, mais problèmes persistants

Bonnes pratiques :

  • Préférez volume nommé au bind mount :
    services:
      db:
        image: postgres
        volumes:
          - pgdata:/var/lib/postgresql/data  # volume
    volumes:
      pgdata:  # géré par Docker, évite NTFS
  • Bind mount obligatoire : projet dans le système de fichiers WSL2 (\\wsl$\Ubuntu\home\...)
  • Évitez les montages inter-disques

Problèmes connus :

  • Montage partition NTFS : bits de permission parfois 777 (effrayant mais contrôlé par NTFS)
  • Liens symboliques limités sur Windows
  • Fin de ligne LF vs CRLF : Git et Docker se mélangent

Équipe multi-plateforme : stratégie unifiée

Mac, Linux et Windows dans la même équipe ?

Configuration recommandée :

docker-compose.yml :

services:
  app:
    build:
      context: .
      args:
        UID: ${UID:-1000}
        GID: ${GID:-1000}
    user: "${UID:-1000}:${GID:-1000}"
    volumes:
      - ./src:/app/src

Dockerfile :

FROM node:18

ARG UID=1000
ARG GID=1000

RUN groupadd -g $GID appuser && \
    useradd -m -u $UID -g $GID appuser

WORKDIR /app
RUN chown appuser:appuser /app

USER appuser

.env.example (partagé) :

# Linux/Mac
# export UID=$(id -u)
# export GID=$(id -g)

# Windows (valeurs fixes)
UID=1000
GID=1000

README.md :

## Démarrer le projet

**Linux/Mac** :
```bash
export UID=$(id -u) GID=$(id -g)
docker-compose up

Windows :

# Dans WSL2, ou docker-compose up direct (défaut 1000)
docker-compose up

**Points clés** :
- build args et variables d'environnement pour la flexibilité
- Linux : UID réel ; Mac/Windows : valeurs par défaut
- Utilisateur créé dans le Dockerfile pour cohérence cross-plateforme
- Documenter les différences par plateforme

### En une phrase

- **Linux** : problèmes les plus visibles, le plus de solutions, le plus proche de la production
- **Mac** : souvent sans souci, mais ne vous endormez pas — configurez proprement
- **Windows** : volume plutôt que bind mount, projet dans le FS WSL2

Équipe multi-plateforme ? build args + user pour que tout le monde fonctionne.

## Cas pratiques : résoudre les scénarios courants

Après théorie, diagnostic, solutions et différences multi-plateforme — cinq scénarios de permissions les plus fréquents, pas à pas.

### Cas 1 : dev local, logs conteneur impossibles à supprimer

**Symptômes** :
Conteneur applicatif local générant des logs. Nettoyage après un moment :
```bash
rm -rf logs/
# rm: cannot remove 'logs/app.log': Permission denied

Diagnostic :

# Étape 1 : propriétaire
$ ls -ln logs/
total 1024
-rw-r--r-- 1 0 0 524288 Dec 17 14:30 app.log
-rw-r--r-- 1 0 0 524288 Dec 17 14:31 error.log

# UID=0, créé par root

# Étape 2 : identité conteneur
$ docker exec myapp id
uid=0(root) gid=0(root) groups=0(root)

# Conteneur en root

# Étape 3 : votre identité
$ id
uid=1000(oden) gid=1000(oden) groups=1000(oden)

# Vous 1000, conteneur 0 — pas de correspondance !

Solution :
Modifier docker-compose.yml, ajouter user :

services:
  myapp:
    image: myapp:latest
    user: "${UID:-1000}:${GID:-1000}"  # ligne clé
    volumes:
      - ./logs:/app/logs

Exécution :

export UID=$(id -u)
export GID=$(id -g)
docker-compose down
docker-compose up

Le conteneur tourne avec votre UID ; les logs vous appartiennent.

En bref : une ligne user, c’est réglé.


Cas 2 : application Django/Flask, permissions fichiers statiques

Symptômes :
Application Web Python collectant les statiques. Après collectstatic :

docker exec webapp python manage.py collectstatic
# Dossier static/ créé

ls -ln static/
# drwxr-xr-x 1 0 0 ...
# propriétaire root — scripts CI ou conteneur nginx sans accès

Cause :
Conteneur en root → fichiers root. Conteneur nginx suivant peut ne pas pouvoir lire.

Solution :
Créer l’utilisateur applicatif dans le Dockerfile :

FROM python:3.11

# Utilisateur applicatif
RUN groupadd -g 1000 appuser && \
    useradd -m -u 1000 -g 1000 appuser

WORKDIR /app

# Dépendances (encore root, apt-get possible)
COPY requirements.txt .
RUN pip install -r requirements.txt

# Code applicatif et droits
COPY --chown=appuser:appuser . /app

# Passer à appuser
USER appuser

CMD ["gunicorn", "myapp.wsgi:application"]

docker-compose.yml :

services:
  webapp:
    build: .
    volumes:
      - static_volume:/app/static

  nginx:
    image: nginx:alpine
    volumes:
      - static_volume:/usr/share/nginx/html/static:ro  # montage lecture seule
    ports:
      - "80:80"

volumes:
  static_volume:

Points clés :

  • Utilisateur correspondant (UID=1000) dans le Dockerfile
  • Volume nommé pour partager les statiques, pas bind mount
  • nginx lit le volume avec son utilisateur — Docker gère les permissions

En bref : utilisateur dans le Dockerfile, volume pour le partage.


Cas 3 : permissions volume base de données

Symptômes :
PostgreSQL ou MySQL au démarrage :

docker-compose up postgres
# postgres: could not open file "/var/lib/postgresql/data/...": Permission denied

Cause :
Les images BDD basculent vers un UID spécifique (ex. postgres UID=999). Bind mount sur un répertoire hôte au mauvais propriétaire.

Diagnostic :

# Répertoire monté
ls -ln ./pgdata
# drwxr-xr-x 1 1000 1000 ...
# propriétaire 1000, conteneur postgres attend 999

# Utilisateur de l'image postgres
docker run --rm postgres:15 id
# uid=999(postgres) gid=999(postgres) groups=999(postgres)

Solution :

Méthode A : volume nommé (recommandé)

services:
  postgres:
    image: postgres:15
    environment:
      POSTGRES_PASSWORD: secret
    volumes:
      - pgdata:/var/lib/postgresql/data  # volume, pas bind mount

volumes:
  pgdata:  # Docker gère les permissions

Méthode B : bind mount obligatoire — préparer les permissions

# Créer et définir le propriétaire
mkdir -p ./pgdata
sudo chown -R 999:999 ./pgdata  # correspond à postgres UID/GID

docker-compose.yml :

services:
  postgres:
    image: postgres:15
    volumes:
      - ./pgdata:/var/lib/postgresql/data

Note : UID selon l’image :

  • PostgreSQL : 999
  • MySQL : 999
  • MongoDB : 999
  • Redis : 999

Pas garanti pour toutes les versions — confirmez avec docker run --rm <image> id.

En bref : volume nommé pour les BDD ; bind mount = chown préalable.


Cas 4 : permissions artefacts de build en CI

Symptômes :
Pipeline CI :

# .gitlab-ci.yml
build:
  script:
    - docker run --rm -v $CI_PROJECT_DIR:/app builder npm run build
    - ls -l dist/  # artefacts
    # -rw-r--r-- 1 root root ... (propriétaire root)
    - cp dist/* /deploy/  # Permission denied !

Runner CI en utilisateur ordinaire, conteneur build en root → artefacts inaccessibles.

Solution :

Méthode A : chown explicite dans le conteneur build

# Dockerfile.builder
FROM node:18

WORKDIR /app
COPY package*.json ./
RUN npm install

COPY . .

# Build et changement de propriétaire
RUN npm run build && \
    chown -R 1000:1000 /app/dist

CMD ["npm", "run", "build"]

Méthode B : —user pour le conteneur build

# .gitlab-ci.yml
build:
  script:
    - docker run --rm --user $(id -u):$(id -g) -v $CI_PROJECT_DIR:/app builder npm run build
    - ls -l dist/  # propriétaire = vous
    - cp dist/* /deploy/  # OK

Méthode C : entrypoint (plus flexible)

FROM node:18

RUN apt-get update && apt-get install -y gosu

COPY entrypoint.sh /
RUN chmod +x /entrypoint.sh

WORKDIR /app
ENTRYPOINT ["/entrypoint.sh"]
CMD ["npm", "run", "build"]

entrypoint.sh :

#!/bin/bash
set -e

# Build
npm run build

# Si OUTPUT_UID défini, changer le propriétaire des artefacts
if [ -n "$OUTPUT_UID" ]; then
    chown -R $OUTPUT_UID:${OUTPUT_GID:-$OUTPUT_UID} /app/dist
fi

Config CI :

build:
  script:
    - docker run --rm -e OUTPUT_UID=$(id -u) -v $CI_PROJECT_DIR:/app builder

En bref : définir le propriétaire des artefacts au build, ou —user.


Cas 5 : permissions Pod Kubernetes

Symptômes :
Déploiement K8s, Pod en échec :

kubectl logs mypod
# Error: EACCES: permission denied, open '/app/data/config.json'

Cause :
securityContext limitant l’utilisateur du Pod, ou fsGroup mal configuré sur le volume.

Diagnostic :

# Entrer dans le Pod
kubectl exec -it mypod -- id
# uid=1000 gid=1000 groups=1000

# Fichiers dans le volume
kubectl exec -it mypod -- ls -ln /app/data
# drwxr-xr-x 2 0 0 ...
# propriétaire root, Pod en 1000 — lecture impossible

Solution :

securityContext dans le spec Pod :

apiVersion: v1
kind: Pod
metadata:
  name: mypod
spec:
  securityContext:
    runAsUser: 1000      # Pod en UID=1000
    runAsGroup: 1000     # GID=1000
    fsGroup: 1000        # groupe des fichiers volume, lecture/écriture

  containers:
  - name: app
    image: myapp:latest
    volumeMounts:
    - name: data
      mountPath: /app/data

  volumes:
  - name: data
    emptyDir: {}

Points clés :

  • runAsUser : UID du processus conteneur
  • runAsGroup : GID du processus
  • fsGroup : groupe propriétaire des fichiers volume + droits lecture/écriture

Avec PersistentVolumeClaim :

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: mypvc
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi

---
apiVersion: v1
kind: Pod
metadata:
  name: mypod
spec:
  securityContext:
    fsGroup: 1000  # groupe des fichiers PVC

  containers:
  - name: app
    image: myapp:latest
    securityContext:
      runAsUser: 1000  # processus en 1000
    volumeMounts:
    - name: storage
      mountPath: /app/data

  volumes:
  - name: storage
    persistentVolumeClaim:
      claimName: mypvc

En bref : runAsUser et fsGroup explicites dans securityContext.


Synthèse des 5 cas

ScénarioSymptômeSolutionMéthode recommandée
Logs dev locauxPermission deniedconfig useruser dans docker-compose.yml
Collecte statiquesnginx sans lectureutilisateur DockerfileUSER appuser + volume
Échec démarrage BDDrépertoire data sans écriturevolume nomméDocker gère les permissions
Artefacts CIétapes suivantes sans accès—user ou entrypointchown au build
Permissions Pod K8serreur EACCESsecurityContextrunAsUser + fsGroup

Scénario différent, solution différente. Diagnostiquez d’abord, puis traitez.

À lire aussi

Conclusion

Vous vous souvenez de la scène du début ? « Permission denied » alors que vous êtes administrateur.

Maintenant vous savez :

Cause profonde : Linux ne reconnaît que UID/GID, pas les noms. Fichiers créés par root conteneur (UID=0) inaccessibles à l’utilisateur hôte (UID=1000).

Diagnostic : trois commandes — ls -ln pour le propriétaire, docker exec <container> id pour l’identité conteneur, docker inspect pour la config de montage. Une minute pour localiser.

Solutions : cinq options —

  1. —user : contournement rapide, tests locaux
  2. Utilisateur Dockerfile : professionnel, projets d’équipe
  3. entrypoint+gosu : init root, exécution en privilèges réduits
  4. userns-remap : isolation entreprise
  5. Rootless Docker : sécurité maximale, fonctionnalités limitées

Multi-plateforme : Mac/Windows ont une couche de conversion — problèmes masqués ; Linux expose les conflits. Ne vous fiez pas à la commodité Mac — configurez proprement dans le Dockerfile.

Pratique : cinq cas — dev local, statiques, BDD, build CI, déploiement K8s. Chaque scénario a sa meilleure approche.

Agir dès aujourd’hui

Aujourd’hui (5 minutes) :

  • Ajoutez user: "${UID:-1000}:${GID:-1000}" à docker-compose.yml
  • docker exec <container> id pour voir l’UID réel de vos conteneurs
  • Testez ls -ln sur un problème de permissions

Cette semaine (1-2 heures) :

  • Améliorez le Dockerfile avec ARG UID/GID et création d’utilisateur
  • Ajoutez les commandes de diagnostic au wiki ou README équipe
  • Partagez cet article si utile

Long terme :

  • Évaluez userns-remap ou rootless si la politique de sécurité l’exige
  • Auditez la config Docker production pour l’isolation des permissions
  • Intégrez des vérifications de permissions dans la CI/CD

Pour finir

Les problèmes de permissions semblent techniques et arides — mais c’est de l’« authentification d’identité ». Le conteneur ne sait pas qui vous êtes sur l’hôte ; il ne voit que des nombres.

Comprenez le mapping UID/GID, et tout devient simple. Plus de chmod 777 aveugle, plus de nuits blanches sur Permission denied.

Choisissez la bonne solution, utilisez les bonnes commandes, comprenez le pourquoi.

C’est réglé.

FAQ

Pourquoi un fichier créé par Docker appartient-il parfois à root sur l'hôte ?
Le noyau compare les UID et GID, pas les noms d'utilisateur. Si le processus du conteneur s'exécute avec l'UID 0, les fichiers d'un bind mount peuvent donc apparaître comme appartenant à root sur l'hôte.
Pourquoi éviter chmod 777 pour corriger un problème de volume Docker ?
Cette commande donne des droits d'écriture à tout le monde et masque la cause réelle. Il vaut mieux aligner les UID/GID avec --user, créer un utilisateur dédié dans l'image ou choisir un volume nommé selon le scénario.
Quelle solution privilégier en production ?
Exécutez le conteneur avec un utilisateur non root défini dans le Dockerfile et donnez-lui uniquement les droits nécessaires. Pour un bind mount, alignez son UID/GID avec l'hôte et validez les permissions avant le démarrage.

20 min de lecture · Publié le: 17 déc. 2025 · Mis à jour le: 27 juil. 2026

Commentaires

Connectez-vous avec GitHub pour laisser un commentaire

Easton BlogEaston Blog