← Retour aux tutoriels

Docker rootless : installation, limites et dépannage des erreurs de permissions

dockerrootlesslinuxnamespacespermissionscgroup-v2subuid-subgidtroubleshooting

Docker rootless : installation, limites et dépannage des erreurs de permissions

Docker « rootless » permet d’exécuter le démon Docker et les conteneurs sans privilèges root, en s’appuyant sur des mécanismes du noyau (namespaces utilisateur, cgroups, etc.) et sur des outils comme RootlessKit et slirp4netns. L’objectif principal est de réduire l’impact d’une compromission : si un conteneur s’échappe, il n’obtient pas directement les privilèges root sur l’hôte.

Ce tutoriel couvre :


1) Comprendre le « rootless » : ce qui change vraiment

En mode classique, Docker fonctionne avec :

En mode rootless :

1.1 Namespaces utilisateur et mappage UID/GID

Le cœur du rootless repose sur userns. Le conteneur peut croire qu’il est root (UID 0), mais sur l’hôte, ce « root » est remappé vers un UID réel non privilégié (ex: 1000). Pour que cela fonctionne, il faut des plages d’UID/GID « subordonnées » déclarées dans :

Ces plages servent à attribuer des identités supplémentaires aux processus du conteneur sans être root sur l’hôte.


2) Pré-requis et vérifications

Les étapes exactes varient selon la distribution, mais les points essentiels sont identiques.

2.1 Vérifier la version de Docker

Docker rootless est supporté depuis Docker 20.10+ (et amélioré au fil du temps). Vérifiez :

docker --version

Si Docker n’est pas installé, installez-le via votre gestionnaire de paquets ou les dépôts officiels Docker.

2.2 Outils nécessaires (paquets)

Selon la distribution, vous aurez besoin de certains paquets :

Exemples :

Debian/Ubuntu :

sudo apt update
sudo apt install -y uidmap slirp4netns fuse-overlayfs

Fedora :

sudo dnf install -y shadow-utils slirp4netns fuse-overlayfs

Sur Fedora, newuidmap/newgidmap peuvent venir de shadow-utils. Sur Debian/Ubuntu, c’est uidmap.

2.3 Vérifier la présence de newuidmap/newgidmap

command -v newuidmap
command -v newgidmap

Vous devriez obtenir un chemin, par exemple /usr/bin/newuidmap.

2.4 Configurer /etc/subuid et /etc/subgid

Vérifiez si votre utilisateur a une plage :

grep -E "^$USER:" /etc/subuid /etc/subgid

Exemple attendu :

/etc/subuid:alice:100000:65536
/etc/subgid:alice:100000:65536

Si rien n’apparaît, ajoutez une plage (exemple avec 65536 IDs) :

sudo usermod --add-subuids 100000-165535 --add-subgids 100000-165535 "$USER"

Déconnectez-vous / reconnectez-vous pour que la session prenne en compte les changements.

2.5 Vérifier cgroups (v2 recommandé)

Rootless fonctionne mieux avec cgroup v2. Vérifiez :

stat -fc %T /sys/fs/cgroup

Sur systemd, vous pouvez aussi vérifier :

mount | grep cgroup

Rootless peut fonctionner en cgroup v1, mais avec des limitations (notamment sur la gestion fine des ressources). Sur certaines distros, cgroup v2 est déjà activé.


3) Installation de Docker rootless

Docker fournit un script d’installation rootless qui configure un service systemd utilisateur et un contexte Docker.

3.1 Lancer le script d’installation

Le script s’appelle souvent dockerd-rootless-setuptool.sh. Selon la distro, il peut être installé avec Docker ou disponible dans docker-ce-rootless-extras.

Sur Debian/Ubuntu (dépôts Docker), installez l’extra :

sudo apt install -y docker-ce-rootless-extras

Puis lancez :

dockerd-rootless-setuptool.sh install

Sortie typique : création d’un service docker.service dans votre session utilisateur, et instructions pour définir DOCKER_HOST.

3.2 Activer le service systemd utilisateur

Activez et démarrez le service :

systemctl --user enable --now docker

Vérifiez l’état :

systemctl --user status docker

Consultez les logs :

journalctl --user -u docker -e

3.3 Configurer l’environnement DOCKER_HOST

En rootless, le socket Docker n’est pas /var/run/docker.sock (réservé à root), mais généralement :

Pour l’exporter dans votre shell :

export DOCKER_HOST=unix:///run/user/$(id -u)/docker.sock

Pour le rendre permanent, ajoutez-le à ~/.bashrc ou ~/.zshrc.

Vérifiez que le client parle au bon démon :

docker context ls
docker info

Dans docker info, cherchez :

3.4 Astuce : éviter la déconnexion du service (linger)

Si vous voulez que Docker rootless continue de tourner même sans session active (utile sur serveur), activez le « linger » :

sudo loginctl enable-linger "$USER"

4) Utilisation quotidienne : images, conteneurs, ports, volumes

4.1 Tester avec un conteneur simple

docker run --rm hello-world

Puis un shell :

docker run --rm -it alpine:3.20 sh
id

Vous verrez uid=0(root) dans le conteneur, mais cela ne signifie pas root sur l’hôte.

4.2 Réseau en rootless : slirp4netns et conséquences

En rootless, Docker ne peut pas créer des interfaces réseau privilégiées (bridge, veth) comme root. Il utilise souvent :

Conséquences :

Vérifiez le réseau :

docker network ls
docker network inspect bridge

4.3 Publier des ports : attention aux ports < 1024

Publier un port est identique côté CLI :

docker run --rm -p 8080:80 nginx:alpine

Mais publier un port privilégié (ex: 80) peut échouer car un utilisateur non-root ne peut pas binder un port <1024.

Option A : utiliser un port > 1024

docker run --rm -p 8080:80 nginx:alpine

Option B : autoriser les ports bas via sysctl (si acceptable)

Vous pouvez abaisser la limite de ports non privilégiés :

sudo sysctl -w net.ipv4.ip_unprivileged_port_start=80

Pour rendre permanent (exemple) :

echo "net.ipv4.ip_unprivileged_port_start=80" | sudo tee /etc/sysctl.d/99-rootless-ports.conf
sudo sysctl --system

Sécurité : cela autorise tous les utilisateurs à binder des ports à partir de 80. À évaluer selon votre contexte.

4.4 Volumes et bind mounts : le point le plus délicat

En rootless, les bind mounts (-v /chemin/hote:/data) sont souvent la source principale d’erreurs de permissions.

Exemple :

mkdir -p ~/data
docker run --rm -it -v ~/data:/data alpine sh
touch /data/test

Si ça échoue, c’est généralement lié au mappage UID/GID et à la propriété des fichiers.

4.4.1 Comprendre le problème UID/GID

Dans le conteneur, vous êtes root (UID 0) mais sur l’hôte, ce root correspond à votre UID réel (ex: 1000) ou à un UID subordonné selon la configuration. Résultat : un fichier créé dans le conteneur peut apparaître sur l’hôte avec un UID « étrange » (ex: 100999) ou être inaccessible.

4.4.2 Utiliser --user pour aligner les permissions

Souvent, le plus simple est d’exécuter le processus du conteneur avec votre UID/GID :

docker run --rm -it \
  --user "$(id -u):$(id -g)" \
  -v ~/data:/data \
  alpine sh

Ainsi, les fichiers créés appartiennent à votre utilisateur sur l’hôte.

4.4.3 Utiliser les options :Z / :z (SELinux)

Sur Fedora/RHEL avec SELinux, les bind mounts peuvent être bloqués par des contextes SELinux. Essayez :

docker run --rm -it -v ~/data:/data:Z alpine sh

En rootless, SELinux peut être plus subtil : selon la distro, certaines opérations de relabel peuvent être limitées. Si :Z échoue, regardez les logs SELinux (ausearch, journalctl).


5) Stockage : overlay2, fuse-overlayfs et performance

En rootless, Docker ne peut pas toujours utiliser overlay2 de manière classique. Il peut basculer sur :

Vérifiez le driver :

docker info | grep -E "Storage Driver|Backing Filesystem"

Si vous voyez vfs, installez/activez fuse-overlayfs et vérifiez que Docker le détecte.


6) Limites connues de Docker rootless (et contournements)

6.1 Accès aux périphériques et fonctionnalités kernel

6.2 Réseau : pas de vrai bridge L2

6.3 Cgroups et limitations de ressources

Selon votre configuration cgroup v2 et systemd, certaines limites (--memory, --cpus) peuvent ne pas s’appliquer comme en rootful, ou nécessiter des réglages.

Vérifiez :

docker run --rm --memory=256m alpine sh -c "cat /sys/fs/cgroup/memory.max 2>/dev/null || true"

6.4 Exposition de ports et services système


7) Dépannage : erreurs de permissions les plus fréquentes

Cette section est volontairement détaillée : en rootless, 80% des problèmes sont des problèmes de permissions (fichiers, sockets, cgroups, user namespaces).

7.1 « Cannot connect to the Docker daemon… permission denied »

Message typique :

Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?
Got permission denied while trying to connect to the Docker daemon socket...

Cause : votre client Docker essaie de parler au socket rootful (/var/run/docker.sock) au lieu du socket rootless.

Correctifs :

  1. Vérifier DOCKER_HOST :
echo "$DOCKER_HOST"
  1. Pointer vers le socket user :
export DOCKER_HOST=unix:///run/user/$(id -u)/docker.sock
  1. Vérifier que le service tourne :
systemctl --user status docker
  1. Vérifier l’existence du socket :
ls -l /run/user/$(id -u)/docker.sock

Si le socket n’existe pas, regardez les logs :

journalctl --user -u docker -n 200 --no-pager

7.2 « newuidmap: write to uid_map failed: Operation not permitted »

Cause : newuidmap/newgidmap manquants, ou /etc/subuid//etc/subgid non configurés, ou noyau limitant les user namespaces.

Diagnostic :

command -v newuidmap newgidmap
grep -E "^$USER:" /etc/subuid /etc/subgid
sysctl kernel.unprivileged_userns_clone 2>/dev/null || true

Correctifs :

sudo sysctl -w kernel.unprivileged_userns_clone=1

Pour persister :

echo "kernel.unprivileged_userns_clone=1" | sudo tee /etc/sysctl.d/99-userns.conf
sudo sysctl --system

Attention : activer les user namespaces non privilégiés peut être un choix de sécurité. Certaines distributions les désactivent par défaut.

7.3 « failed to mount overlay: permission denied » / stockage en échec

Cause : overlayfs non utilisable en rootless, fuse-overlayfs absent, ou restrictions du FS.

Diagnostic :

docker info | grep -E "Storage Driver|Rootless|Docker Root Dir"

Regarder les logs :

journalctl --user -u docker -e

Correctifs :

  1. Installer fuse-overlayfs :
sudo apt install -y fuse-overlayfs
  1. Redémarrer Docker rootless :
systemctl --user restart docker
  1. Vérifier que le driver n’est pas vfs :
docker info | grep "Storage Driver"

Si vous êtes sur un FS exotique ou monté avec des options restrictives, essayez de placer le Docker Root Dir sur un FS plus standard (ext4/xfs). En rootless, le répertoire est souvent dans ~/.local/share/docker.

7.4 « permission denied » sur un bind mount (volume)

Exemple :

docker run --rm -v /srv/data:/data alpine sh -c "touch /data/x"
# touch: /data/x: Permission denied

Causes fréquentes :

Étapes de diagnostic :

  1. Vérifier les permissions hôte :
ls -ld /srv/data
getfacl -p /srv/data 2>/dev/null || true
  1. Tester avec votre UID dans le conteneur :
docker run --rm -it --user "$(id -u):$(id -g)" -v /srv/data:/data alpine sh
  1. Si SELinux (Fedora/RHEL) :
ls -Z /srv/data 2>/dev/null || true

Puis tester :

docker run --rm -it -v /srv/data:/data:Z alpine sh

Correctifs possibles :

sudo chown -R "$USER:$USER" /srv/data
# ou
sudo chmod -R u+rwX /srv/data
sudo setfacl -m u:$USER:rwx /srv/data
sudo setfacl -d -m u:$USER:rwx /srv/data

7.5 « bind: permission denied » lors du publish de port

Exemple :

docker run --rm -p 80:80 nginx:alpine
# Error starting userland proxy: listen tcp 0.0.0.0:80: bind: permission denied

Cause : port < 1024.

Solutions :

docker run --rm -p 8080:80 nginx:alpine

7.6 « cgroup: permission denied » / limites de ressources non appliquées

Symptômes : --memory, --pids-limit, --cpus ne fonctionnent pas, ou erreurs dans les logs.

Diagnostic :

docker info | grep -E "Cgroup Driver|Cgroup Version|rootless"

Vérifier cgroup v2 :

stat -fc %T /sys/fs/cgroup

Correctifs :

7.7 « operation not permitted » lors de certaines syscalls (ex: ping, mount, setcap)

En rootless, certaines capacités Linux ne peuvent pas être accordées réellement.

Contournements :

7.8 Problèmes de DNS / accès Internet depuis les conteneurs

Cause : slirp4netns, resolv.conf, VPN, DNS split-horizon.

Diagnostic :

docker run --rm alpine sh -c "cat /etc/resolv.conf; nslookup example.com 2>/dev/null || true"

Tester connectivité :

docker run --rm alpine sh -c "wget -qO- https://example.com >/dev/null && echo OK"

Correctifs :

docker run --rm --dns 1.1.1.1 --dns 8.8.8.8 alpine nslookup example.com
mkdir -p ~/.config/docker
cat > ~/.config/docker/daemon.json <<'EOF'
{
  "dns": ["1.1.1.1", "8.8.8.8"]
}
EOF

systemctl --user restart docker

8) Configuration utile du démon rootless

8.1 Où sont les fichiers ?

8.2 Exemple de daemon.json

mkdir -p ~/.config/docker
nano ~/.config/docker/daemon.json

Exemple :

{
  "log-driver": "json-file",
  "log-opts": { "max-size": "10m", "max-file": "3" },
  "dns": ["1.1.1.1", "8.8.8.8"]
}

Puis :

systemctl --user restart docker

9) Bonnes pratiques en rootless

  1. Préférer --user $(id -u):$(id -g) pour les conteneurs qui écrivent sur des bind mounts.
  2. Éviter de binder des répertoires système (/srv, /var/lib/...) sans ajuster propriétaires/ACL.
  3. Documenter la gestion des ports (80/443) : soit reverse proxy rootful, soit sysctl, soit ports >1024.
  4. Sur serveurs multi-utilisateurs, rootless réduit le risque, mais ne remplace pas :
    • mises à jour,
    • images minimales,
    • politiques seccomp/apparmor,
    • scanning d’images.
  5. Sur workloads exigeants (réseau perf, stockage perf, GPU), évaluer si rootless convient.

10) Scénarios pratiques

10.1 Lancer un service web en rootless sur 8080

docker run -d --name web \
  -p 8080:80 \
  nginx:alpine

Tester :

curl -I http://127.0.0.1:8080
docker logs web

10.2 Développement avec volume et UID aligné

mkdir -p ~/projet
docker run --rm -it \
  --user "$(id -u):$(id -g)" \
  -v ~/projet:/work \
  -w /work \
  node:20-alpine sh

Dans le conteneur :

node -v
npm init -y

Sur l’hôte, les fichiers appartiendront à votre utilisateur.

10.3 Diagnostiquer un conteneur qui ne peut pas écrire

docker run --rm -it -v ~/data:/data alpine sh -c '
  id
  ls -ld /data
  touch /data/test || (echo "ECHEC"; exit 1)
'

Si échec, relancez avec --user :

docker run --rm -it --user "$(id -u):$(id -g)" -v ~/data:/data alpine sh -c '
  id
  touch /data/test && echo OK
'

11) Désinstallation / retour arrière

Si vous voulez retirer l’installation rootless :

  1. Arrêter le service user :
systemctl --user stop docker
systemctl --user disable docker
  1. Supprimer les fichiers (attention : cela supprime images/volumes rootless) :
rm -rf ~/.local/share/docker
rm -rf ~/.config/docker
  1. Optionnel : retirer docker-ce-rootless-extras si installé :
sudo apt remove docker-ce-rootless-extras

12) Checklist rapide de dépannage


Conclusion

Docker rootless est une excellente option quand vous voulez réduire l’exposition aux privilèges root, notamment sur des postes de dev, des environnements partagés, ou des serveurs où vous souhaitez limiter l’impact d’un conteneur compromis. En échange, vous acceptez des compromis : réseau moins « bas niveau », limitations sur ports privilégiés et certaines capacités kernel, et surtout une gestion des volumes plus exigeante.

Si vous me donnez votre distribution, votre version de Docker, et un extrait de docker info + l’erreur exacte (et la commande qui la déclenche), je peux proposer un diagnostic ciblé (réseau, stockage, userns, SELinux, cgroups).