Cybersécurité

Docker rootless et sécurité conteneur : tutoriel 2026

11 min de lecture

Le mode rootless de Docker (et de Podman par défaut) réduit massivement la surface d’attaque : si un attaquant compromet un conteneur, il n’obtient pas root sur l’hôte.

Voir notre guide Docker complet.

Activer Docker rootless

# Installer dépendances
sudo apt install -y uidmap dbus-user-session

# En tant qu'utilisateur normal
dockerd-rootless-setuptool.sh install

# Activer au démarrage
systemctl --user enable docker
loginctl enable-linger $(whoami)

# Variables d'env
echo 'export PATH=/usr/bin:$PATH' >> ~/.bashrc
echo 'export DOCKER_HOST=unix:///run/user/1000/docker.sock' >> ~/.bashrc

Limitations rootless

  • Pas de bind sur ports < 1024 sans setcap ou config sysctl
  • Pas de cgroup v1 (besoin v2 — défaut moderne)
  • Networking : slirp4netns, légèrement plus lent que bridge
  • Volumes : permissions à gérer attentivement

Sécurité conteneur essentielle

# USER non-root dans Dockerfile
USER 1000:1000

# Drop capabilities
docker run --cap-drop=ALL --cap-add=NET_BIND_SERVICE myapp

# Read-only FS
docker run --read-only --tmpfs /tmp myapp

# Empêcher escalade
docker run --security-opt=no-new-privileges myapp

# Limits
docker run --memory=512m --cpus="0.5" myapp

Scan d’images

# Trivy : scan vulnérabilités
trivy image myapp:latest

# Scout (Docker)
docker scout cves myapp:latest

# Snyk
snyk container test myapp:latest

Intégrer dans le CI/CD avant push en prod.

Hardening Docker host

  • Daemon socket : ne JAMAIS exposer publiquement (port 2375 unsecured)
  • Live-restore activé : containers survivent au restart daemon
  • userns-remap : isolement supplémentaire des namespaces
  • Audit logs : auditd sur /var/lib/docker
  • CIS Docker Benchmark : suivre les recommandations

À lire ensuite

Pour appliquer ce tutoriel sur un vrai serveur

Hostinger reste l’option la plus simple pour démarrer. Lien partenaire — votre achat soutient le blog sans surcoût.

Réserver un VPS Hostinger →

Lien d affiliation. Si vous achetez via ce lien, le blog reçoit une petite commission sans surcoût pour vous.

Pourquoi exécuter Docker en mode rootless

Par défaut, le daemon Docker tourne en root. Une faille dans dockerd ou un container mal isolé peut donner un accès root à votre VPS — scénario catastrophe pour un serveur hébergeant des données clients ou des workloads multi-tenant. Le mode rootless lance le daemon sous un utilisateur non privilégié et utilise les user namespaces Linux pour mapper les UID. Un attaquant qui s’évade du container atterrit dans votre user space, pas root.

Pour un dev à Dakar qui partage un VPS avec d’autres apps, ou un étudiant à Abidjan qui prototype, le rootless réduit drastiquement le risque. La contrepartie : quelques limitations réseau et performance que ce tutoriel détaille.

Étape 1 : prérequis kernel et paquets

Le rootless nécessite un kernel Linux 5.13+ avec cgroup v2. Ubuntu 22.04 et 24.04 sont compatibles. Vérifiez avant de démarrer pour éviter une demi-installation.

uname -r
# Doit afficher 5.13+ idéalement 6.x

stat -fc %T /sys/fs/cgroup
# Doit afficher cgroup2fs

sudo apt install -y uidmap dbus-user-session fuse-overlayfs slirp4netns

Résultat attendu : cgroup2fs confirmé. Si tmpfs apparaît, vous êtes en cgroup v1. Activez v2 avec systemd.unified_cgroup_hierarchy=1 dans GRUB et redémarrez.

Étape 2 : installer Docker en mode rootless

Le script officiel dockerd-rootless-setuptool.sh configure tout sous votre utilisateur. Ne lancez jamais avec sudo : il doit s’exécuter en tant que vous.

curl -fsSL https://get.docker.com/rootless | sh
# Suivre les instructions du script

# Ajouter au .bashrc
export PATH=/home/$USER/bin:$PATH
export DOCKER_HOST=unix:///run/user/$(id -u)/docker.sock
source ~/.bashrc

systemctl --user start docker
systemctl --user enable docker
loginctl enable-linger $USER

Vous devriez obtenir : docker version affiche un client et un serveur, sans Cannot connect to the Docker daemon. Le linger garantit que le daemon survit à votre déconnexion SSH.

Étape 3 : vérifier que tout tourne sans root

Lancez un container test et inspectez les processus. Confirmation que dockerd et le container tournent sous votre UID.

docker run --rm hello-world
ps -u $USER | grep -E 'dockerd|hello'
id -u  # Votre UID hôte

Vous devriez obtenir : aucun processus root lié à Docker. Toute la chaîne tourne sous votre UID utilisateur. Si vous voyez un dockerd avec PID 1 sous root, vous avez démarré le mauvais service — désactivez le service système avec sudo systemctl disable --now docker.

Étape 4 : limitations à connaître

En rootless, vous ne pouvez pas binder de ports inférieurs à 1024 (80, 443) sans configuration supplémentaire. La solution propre : un reverse proxy hôte (Caddy ou Nginx) qui écoute en 80/443 et forward vers vos containers en 8080/8443. Alternative : sudo setcap cap_net_bind_service=ep $(which rootlesskit) mais ça ouvre un trou de sécurité.

Le réseau utilise slirp4netns par défaut, qui plafonne autour de 1 Gbps. Pour des charges réseau intensives, basculez sur pasta avec --driver pasta, plus rapide et compatible avec le mode rootless depuis 2024.

Étape 5 : configurer un reverse proxy Caddy hôte

Caddy installé en paquet apt tourne avec sa propre user et écoute légalement sur 80/443. Il forward vers votre container rootless.

sudo apt install -y caddy

# /etc/caddy/Caddyfile
api.itskillscenter.io {
    reverse_proxy localhost:8080
}

sudo systemctl reload caddy

Ce que vous devez voir : Caddy obtient automatiquement un certificat Let’s Encrypt et proxifie vers votre container. Vérifiez avec curl -I https://api.itskillscenter.io qui doit renvoyer un 200 ou 404 (selon votre app), pas un 502.

Étape 6 : utiliser des volumes en rootless

Les volumes nommés se créent et s’utilisent comme en mode root, mais sont stockés dans ~/.local/share/docker. Les bind mounts hôte fonctionnent uniquement sur des fichiers que votre user peut lire.

docker volume create app-data
docker run -d --name pg \
  -v app-data:/var/lib/postgresql/data \
  -e POSTGRES_PASSWORD=ChangeMe2026 \
  postgres:17-alpine
docker volume ls

Sortie attendue : un volume app-data persistant entre les redémarrages du container. Pour un backup, docker run --rm -v app-data:/data alpine tar czf - /data > backup.tar.gz.

Étape 7 : limites de ressources sans capabilities root

Les flags --memory et --cpus fonctionnent en rootless si cgroup v2 est actif et délégué à votre user. Vérifiez avec cat /sys/fs/cgroup/user.slice/user-$(id -u).slice/cgroup.controllers.

docker run -d --name api \
  --memory 512m --cpus 0.5 \
  -p 8080:3000 my-api:1.0
docker stats --no-stream

Ce que vous devez voir : la colonne MEM USAGE / LIMIT affiche bien … / 512MiB. Si la limite n’apparaît pas (vous voyez … / 7.7GiB), cgroup v2 n’est pas délégué. Solution : sudo systemctl edit user@.service et ajouter Delegate=yes.

Étape 8 : surveillance et logs

Les logs sont accessibles via journalctl --user -u docker pour le daemon, et docker logs <container> pour les workloads. Pour un monitoring centralisé, branchez Grafana Loki en pointant le driver de log json-file vers un agent Promtail.

journalctl --user -u docker --since "1 hour ago"
docker logs --tail 100 -f api

Résultat type : les logs streamés en temps réel. Pour un VPS sans stack monitoring, --tail 100 -f reste le plus rapide à lancer en cas d’incident.

Pièges fréquents

Premier piège : tenter d’utiliser --privileged en rootless. La plupart des privilèges ne peuvent pas être octroyés sans root sur l’hôte — le flag est partiellement ignoré silencieusement. Si vous avez vraiment besoin de privileged, votre design container est probablement mauvais. Deuxième piège : iptables et NAT custom ne fonctionnent pas en user namespace. Solution : utiliser le réseau bridge par défaut et publier les ports. Troisième piège : monter /var/run/docker.sock dans un container (anti-pattern Docker-in-Docker) ne fonctionne pas comme attendu en rootless car le socket est dans ~/.docker/run/.

Cas d’usage typiques

Cas 1 — VPS partagé entre devs : chaque dev a son daemon rootless, isolation forte sans privilèges croisés. Cas 2 — laboratoire local pour étudiants : pas de risque qu’un étudiant break la machine en root. Cas 3 — CI/CD self-hosted runner : le runner GitHub Actions tourne en rootless, isolation par builds. Cas 4 — tests sécurité sur containers : le rootless permet de tester des images suspectes sans donner root à un workload douteux.

Voir aussi notre comparatif Docker vs Podman et le tutoriel Caddy reverse proxy qui complètent ce setup.

Étape 9 : durcissement supplémentaire avec seccomp et AppArmor

Le mode rootless réduit déjà la surface, mais Docker applique par défaut un profil seccomp restrictif qui filtre les appels système dangereux. Vérifiez qu’il est actif et n’est pas désactivé par mégarde dans vos commandes docker run.

docker info | grep -i seccomp
# Doit afficher : seccomp Profile: builtin

# Lister les capabilities du container :
docker run --rm alpine sh -c 'cat /proc/self/status | grep Cap'

Résultat attendu : un ensemble de capabilities limité, sans CAP_SYS_ADMIN ni CAP_NET_ADMIN. Tout flag --security-opt seccomp=unconfined doit être justifié — il désactive 60+ filtres et augmente massivement le risque d’évasion en cas de faille kernel.

Étape 10 : utiliser des images minimalistes

Une image basée sur Alpine pèse 5 Mo, contre 80 Mo pour Ubuntu et 800 Mo pour une image full Debian avec build-essential. Moins de paquets égale moins de surface d’attaque et téléchargement plus rapide depuis Dakar (où la bande passante peut osciller selon l’heure).

FROM alpine:3.20
RUN apk add --no-cache nodejs npm
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
USER 1000
CMD ["node", "server.js"]

Sortie de référence : une image finale de 60-90 Mo selon vos dépendances Node, contre 200-400 Mo en partant de node:22. La directive USER 1000 évite que l’application tourne en root dans le container, double protection avec le rootless hôte.

Étape 11 : auditer les images avec Trivy

Trivy détecte les CVE des paquets de votre image avant déploiement. Intégrez-le en CI ou lancez-le en local avant chaque docker push.

sudo apt install -y trivy
trivy image --severity HIGH,CRITICAL my-api:1.0
trivy config Dockerfile

Sortie attendue : la liste des CVE classées par sévérité avec le numéro et la version corrigée. Cible : zéro CRITICAL ouvert avec patch disponible. Les HIGH peuvent être tolérées si elles concernent un paquet non exposé au réseau (ex. une lib utilitaire interne).

Étape 12 : sauvegardes et restauration rootless

Les volumes et données sont dans votre ~/.local/share/docker. Une sauvegarde simple consiste à arrêter le daemon et tar le dossier — ou mieux, exporter chaque volume séparément pour une restauration sélective.

systemctl --user stop docker
tar czf docker-backup-$(date +%F).tar.gz \
  -C ~/.local/share docker
systemctl --user start docker

Résultat attendu : une archive contenant tous vos volumes et metadata Docker. Stockez-la sur un stockage objet externe (R2, B2, MinIO) avec chiffrement client-side via age pour les données sensibles.

Récapitulatif checklist sécurité

Configuration rootless propre validée par cinq points : daemon tournant sous user, cgroup v2 délégué pour limites de ressources, profil seccomp builtin actif, images Alpine ou distroless avec USER non-root, scan Trivy sans CRITICAL ouvert. Couplé à un reverse proxy Caddy en façade et un firewall UFW restrictif sur l’hôte, ce setup résiste à la grande majorité des attaques opportunistes ciblant les VPS exposés sur Internet, fréquentes en zone ouest-africaine où les bots scannent en continu.

FAQ Docker rootless

Le rootless est-il aussi performant que le mode root ? Quasi identique pour les workloads CPU et mémoire. Le réseau slirp4netns plafonne à environ 1 Gbps ; pour des charges réseau intensives, basculez sur pasta qui efface cette limite. Le démarrage à froid d’un container est environ 100 à 200 ms plus lent que root, négligeable pour un service long-running.

Puis-je faire tourner Kubernetes en rootless ? Oui, k3s et kind supportent le mode rootless depuis 2022. Pour la production multi-nœuds, restez sur des nœuds dédiés en root, le rootless est surtout pertinent pour le poste de dev et les CI runners.

Compose v2 fonctionne-t-il avec le rootless ? Oui, totalement transparent. La commande docker compose up utilise votre socket utilisateur sans configuration supplémentaire. Tous les fichiers docker-compose.yml existants tournent sans modification.

Comment migrer une installation Docker root existante vers rootless ? Exportez vos volumes critiques en tar, désinstallez le daemon système, installez le rootless, recréez les volumes et réimportez les données. Comptez une heure pour un setup avec 5-10 services et quelques gigaoctets de données.

Mise en production réelle

Pour un déploiement en production sur VPS Hetzner CX22 ou OVH VPS Starter, le pattern recommandé combine quatre couches : firewall UFW autorisant uniquement 22, 80 et 443, Docker rootless pour les workloads applicatifs, Caddy hôte en reverse proxy avec HTTPS automatique, et fail2ban pour bannir les IP qui spam le SSH. Ce stack tient sur 2 vCPU et 4 Go de RAM avec une dizaine de containers légers, pour un coût mensuel inférieur à 5 000 FCFA selon le fournisseur. Les sauvegardes vers Backblaze B2 ou Cloudflare R2 ajoutent moins de 1 USD par mois pour les volumes typiques d’un site métier.

Lectures complémentaires sur l’architecture serveur, consultez aussi notre guide UFW et fail2ban pour VPS.

Cette configuration sert sereinement plusieurs petits sites métier ouest-africains en production stable.

Partager