15 - Permissions et utilisateurs
Ce que tu vas apprendre
- Pourquoi les conteneurs tournent en root par défaut et pourquoi c'est un problème
- L'instruction USER dans le Dockerfile
- Le mapping UID/GID entre l'hote et le conteneur
- L'enfer des permissions avec les bind mounts
- Les solutions : fixuid, gosu, et le flag --user
Prerequisites
- Avoir suivi l'article sur les variables d'environnement
- Comprendre les bases des permissions Unix (chmod, chown, UID/GID)
Tu créés un fichier de log depuis ton conteneur. Tu regardes sur ta machine : le fichier appartient a root. Tu ne peux pas le supprimer sans sudo. Ca t'est deja arrive ? C'est le problème de permissions Docker le plus courant, et il rend fou les gens qui debutent.
Root par défaut
Par défaut, les processus dans un conteneur Docker tournent en tant que root (UID 0). Si tu n'as jamais ajoute d'instruction USER dans ton Dockerfile, tout tourne en root.
En dev, ca semble anodin. En production, c'est un risque. Si un attaquant exploite une faille dans ton application conteneurisee, il obtient les droits root dans le conteneur. Et dans certaines configurations, root dans le conteneur = root sur l'hote.
bash# Verifie sous quel utilisateur tourne ton conteneur
docker compose exec app whoami
# root
L'instruction USER
La solution la plus directe : créer un utilisateur non-root dans ton Dockerfile :
dockerfileFROM node:20-alpine
# Creer un utilisateur avec un UID specifique
RUN addgroup -g 1001 appgroup && \
adduser -u 1001 -G appgroup -D appuser
WORKDIR /app
COPY --chown=appuser:appgroup . .
USER appuser
CMD ["node", "dist/index.js"]
A partir de l'instruction USER, toutes les commandes RUN, CMD et ENTRYPOINT suivantes s'executent en tant que appuser.
Les images officielles Node incluent deja un utilisateur node (UID 1000). Tu peux l'utiliser directement :
dockerfileFROM node:20-alpine
WORKDIR /app
COPY --chown=node:node . .
USER node
CMD ["node", "dist/index.js"]
Sur paltemps.fr, tous mes conteneurs de production tournent avec USER node. Ca m'a pris 5 minutes a configurer et ca ferme un vecteur d'attaque.
UID/GID : le lien entre hote et conteneur
Les permissions Linux ne connaissent pas les noms d'utilisateurs. Elles fonctionnent avec des numeros : UID (User ID) et GID (Group ID). Quand un fichier appartient a UID 1000 dans le conteneur, il appartient aussi a UID 1000 sur l'hote, meme si cet UID correspond a un utilisateur différent.
bash# Dans le conteneur
id
# uid=1001(appuser) gid=1001(appgroup)
# Sur l'hote, le fichier cree appartient a UID 1001
ls -ln /path/to/mounted/file
# -rw-r--r-- 1 1001 1001 42 Mar 29 docker-15-permissions.md
Si ton utilisateur sur l'hote est UID 1000, et que le conteneur créé des fichiers en tant que UID 1001, tu ne pourras pas les modifier sans changer les permissions.
L'enfer des bind mounts
Les bind mounts sont là où les permissions deviennent penibles. Le scénario classique :
- Tu montes
./srcdans le conteneur - Le conteneur tourne en root (UID 0)
- L'app créé des fichiers (logs, caches, node_modules)
- Ces fichiers appartiennent a root sur ta machine
- Ton éditeur ne peut pas les modifier,
rmdemande sudo
Le problème inverse existe aussi : tu montes un répertoire qui appartient a ton utilisateur (UID 1000) sur l'hote, mais le conteneur tourne en tant que appuser (UID 1001). L'app ne peut pas écrire dans le volume.
Solution 1 : matcher les UIDs
La solution la plus simple : faire en sorte que l'utilisateur du conteneur ait le meme UID que ton utilisateur hote :
dockerfileFROM node:20-alpine
ARG USER_UID=1000
ARG USER_GID=1000
RUN deluser node && \
addgroup -g $USER_GID appgroup && \
adduser -u $USER_UID -G appgroup -D appuser
USER appuser
Et dans Compose :
yamlservices:
app:
build:
context: .
args:
USER_UID: 1000
USER_GID: 1000
Ca fonctionne bien quand tout le monde dans l'équipe a le meme UID (c'est souvent le cas : 1000 est le premier utilisateur créé sur Linux). Sur Mac et Windows avec Docker Desktop, le problème est gere differemment car les fichiers passent par la VM.
Solution 2 : le flag --user
Tu peux forcer l'UID/GID au lancement sans modifier le Dockerfile :
bashdocker run --user $(id -u):$(id -g) myimage
Ou dans Compose :
yamlservices:
app:
user: "${UID:-1000}:${GID:-1000}"
Le problème : si l'utilisateur n'existe pas dans /etc/passwd du conteneur, certains outils se plaignent ("I have no name!"). Ca fonctionne quand meme pour la plupart des cas.
Solution 3 : fixuid
fixuid est un outil qui ajuste dynamiquement l'UID/GID de l'utilisateur du conteneur au démarrage :
dockerfileFROM node:20-alpine
RUN addgroup -g 1000 appgroup && \
adduser -u 1000 -G appgroup -D appuser
# Installer fixuid
RUN USER=appuser && \
GROUP=appgroup && \
curl -SsL https://github.com/boxboat/fixuid/releases/download/v0.6.0/fixuid-0.6.0-linux-amd64.tar.gz | tar -C /usr/local/bin -xzf - && \
chown root:root /usr/local/bin/fixuid && \
chmod 4755 /usr/local/bin/fixuid && \
mkdir -p /etc/fixuid && \
printf "user: $USER\ngroup: $GROUP\n" > /etc/fixuid/config.yml
USER appuser:appgroup
ENTRYPOINT ["fixuid"]
CMD ["node", "dist/index.js"]
Au démarrage, fixuid change l'UID/GID de appuser pour matcher celui passe avec --user. C'est transparent pour l'application.
Solution 4 : gosu pour changer d'utilisateur
gosu est un remplacement de su et sudo adapte aux conteneurs :
dockerfileFROM node:20-alpine
RUN apk add --no-cache gosu
COPY entrypoint.sh /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
bash#!/bin/sh
# entrypoint.sh
# Ajuster les permissions si necessaire
chown -R node:node /app/data
# Lancer l'app en tant que node
exec gosu node node dist/index.js
L'entrypoint tourne en root pour ajuster les permissions, puis gosu drop les privileges avant de lancer l'application.
Les bonnes pratiques
- Ajoute toujours
USERdans tes Dockerfiles de production - Utilise
--chowndans les instructionsCOPYetADD - Pour le dev avec bind mounts, aligne l'UID du conteneur sur celui de l'hote
- Ne monte jamais un bind mount en écriture si tu n'en as pas besoin (
:ro) - Teste les permissions dans ta CI :
docker run --user 1000:1000 myimage whoami
Résumé
- Les conteneurs tournent en root par défaut : toujours ajouter
USERen production - Les permissions sont basees sur les UID/GID, pas les noms d'utilisateurs
- Les bind mounts heritent des permissions de l'hote : aligner les UIDs resout la plupart des problèmes
- fixuid et gosu sont des outils specialises pour les cas complexes
- Le flag
--userforce l'UID/GID sans modifier l'image
Navigation : Precedent : 14 - Variables d'environnement et secrets | Suivant : 16 - Docker en monorepo
Sources
- Dockerfile USER référencé par Docker
- fixuid - dynamic UID/GID mapping par BoxBoat
- Understanding Linux permissions in Docker par Marc Campbell