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

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.
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 :
- Sur l’hôte Linux, vous démarrez un conteneur en tant qu’utilisateur ordinaire UID=1000
- Le processus dans le conteneur s’exécute par défaut en root (UID=0)
- root dans le conteneur crée un fichier, par ex.
/app/logs/output.log - Ce fichier est mappé sur l’hôte via bind mount :
./logs/output.log - Sur l’hôte, le propriétaire du fichier apparaît comme root (UID=0)
- 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 visiblesSource: chemin hôte — faitesls -lnsur ce cheminRW: true = lecture/écriture, false = lecture seuleMode: options spéciales (ex.:zou:Zpour SELinux)
Flux de diagnostic en une minute
En cas de problème de permissions, dans cet ordre :
- Fichier d’abord :
ls -lnpour voir UID/GID - Conteneur ensuite :
docker exec <container> idpour l’identité du processus - Comparer : si l’UID du conteneur et le propriétaire du fichier diffèrent de votre UID hôte → conflit de permissions
- Confirmer la config :
docker inspectpour 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 conteneurrunAsGroup: GID du processusfsGroup: 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énario | Symptôme | Solution | Méthode recommandée |
|---|---|---|---|
| Logs dev locaux | Permission denied | config user | user dans docker-compose.yml |
| Collecte statiques | nginx sans lecture | utilisateur Dockerfile | USER appuser + volume |
| Échec démarrage BDD | répertoire data sans écriture | volume nommé | Docker gère les permissions |
| Artefacts CI | étapes suivantes sans accès | —user ou entrypoint | chown au build |
| Permissions Pod K8s | erreur EACCES | securityContext | runAsUser + fsGroup |
Scénario différent, solution différente. Diagnostiquez d’abord, puis traitez.
À lire aussi
- Volumes Docker : le guide complet de la persistance des données
- Types de montage Docker comparés : bind mount, volume, tmpfs
- Exécuter Docker en utilisateur non-root : durcissement des conteneurs en pratique
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 —
- —user : contournement rapide, tests locaux
- Utilisateur Dockerfile : professionnel, projets d’équipe
- entrypoint+gosu : init root, exécution en privilèges réduits
- userns-remap : isolation entreprise
- 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> idpour voir l’UID réel de vos conteneurs- Testez
ls -lnsur 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 ?
Pourquoi éviter chmod 777 pour corriger un problème de volume Docker ?
Quelle solution privilégier en production ?
20 min de lecture · Publié le: 17 déc. 2025 · Mis à jour le: 27 juil. 2026
Guide pratique Docker
Si vous arrivez depuis la recherche, le plus rapide est de passer à l’article précédent ou suivant de cette série.
Précédent
Comparaison des montages Docker : Volume vs Bind Mount — guide de choix (avec tests de performance)
Comparez en profondeur les trois types de montage Docker, résolvez npm install 3× plus lent sur Mac, et choisissez Volume, Bind Mount ou tmpfs grâce à un arbre de décision et des scénarios réels.
Partie 16 sur 38
Suivant
Modes réseau Docker : bridge/host/none/container — performance et choix de scénarios
Analyse approfondie des quatre modes réseau Docker (bridge/host/none/container) : principes, comparaison de performance et cas d'usage, pour faire le bon choix de configuration réseau, avec exemples pratiques et guide de décision.
Partie 18 sur 38



Commentaires
Connectez-vous avec GitHub pour laisser un commentaire