E-NO
DevOps 12 min de lecture

Durcissement de la sécurité Docker avec des exemples pratiques : guide de mise en œuvre

calendar_today Publié : 2026-07-28
update Dernière mise à jour : 2026-07-28
analytics Efficacité SEO : 97%
Illustration du guide technique pour « Durcissement de la sécurité Docker avec des exemples pratiques : guide de mise en œuvre ».

Introduction

Ce guide pratique et progressif explique comment durcir Docker avec des exemples que vous pouvez exécuter dans un pilote contrôlé. L'objectif est d'établir une base saine, d'appliquer des changements de configuration sûrs, de vérifier les résultats après chaque étape et de pouvoir revenir en arrière si quelque chose se casse. Le fil conducteur : moindre privilège, secrets, permissions et exposition réseau minimale, avec des changements mesurables et réversibles.

Note de périmètre : les exemples ci-dessous sont des modèles sûrs et adaptables à votre contexte.

---

Inventaire des versions et de l'environnement

Avant tout changement, capturez l'état actuel pour comparer, valider et revenir en arrière si besoin. Limitez ce travail à un petit conteneur ou service pilote que vous pouvez inspecter localement.

  1. Enregistrer les versions et paramètres clés
  • Commandes d'exemple (exemples construits) :
# Enregistrer la version de Docker et les infos du noyau
sudo docker version
sudo docker info
uname -a

# Sauvegarder dans un instantané daté
sudo docker version > baseline-$(date +%F).txt
sudo docker info >> baseline-$(date +%F).txt
  1. Identifier le pilote et la topologie
  • Choisir un service simple pour le pilote (par exemple, un petit serveur HTTP lié à localhost).
  • Déterminer les chemins hôte minimaux à monter.
  • Définir l'ensemble le plus restreint de ports exposés et de réseaux nécessaires.
  1. Confirmer les prérequis
  • Hôte Linux avec Docker Engine supporté.
  • Possibilité d'éditer /etc/docker/daemon.json et de redémarrer Docker.
  • Droits sudo pour les étapes administratives.

Pourquoi commencer petit ? Un pilote étroit et mesurable est facile à inspecter, permet de quantifier l'effet de chaque contrôle et réduit la reprise quand des problèmes surviennent plus tard.

---

Chemin de configuration sûre

Appliquez le durcissement par couches et vérifiez après chaque couche. Le but est d'atteindre le moindre privilège et une exposition minimale sans casser la fonctionnalité requise.

1. Contrôle d'accès autour du démon Docker

  • N'accordez pas largement l'appartenance au groupe docker ; cela revient pratiquement à donner les droits root sur l'hôte.
  • Préférez l'exécution via sudo pour les administrateurs. Pour les développeurs, envisagez Docker rootless sur les machines de dev si c'est pertinent.

Pilote optionnel : Docker rootless sur un poste développeur (exemple construit)

# Prérequis : s'assurer que 'uidmap' est installé
sudo apt-get install -y uidmap
export XDG_RUNTIME_DIR=/run/user/$(id -u)
export PATH=/usr/bin:$PATH

# Configuration rootless (exemple construit)
dockerd-rootless-setuptool.sh install
systemctl --user enable --now docker
export DOCKER_HOST=unix:///run/user/$(id -u)/docker.sock

Vérifiez avec docker info que le mode rootless est signalé si utilisé.

2. Défauts du démon qui réduisent le rayon d'impact

Éditez /etc/docker/daemon.json puis redémarrez Docker. Sauvegardez le fichier au préalable.

Exemple de daemon.json (exemple construit) :

{
  "icc": false,
  "live-restore": true,
  "userns-remap": "default",
  "log-driver": "local",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  }
}
  • icc: false bloque la communication inter-conteneurs sur le bridge par défaut sauf mise en réseau explicite.
  • live-restore: true aide les conteneurs à survivre à un redémarrage du démon.
  • userns-remap: default mappe root du conteneur vers un UID/GID non privilégié sur l'hôte.
  • Le logging local borné évite de saturer le disque.

Appliquer et redémarrer :

sudo cp /etc/docker/daemon.json /etc/docker/daemon.json.bak.$(date +%F)
sudo systemctl restart docker

Note sur le remap d'espace de noms utilisateur : après activation, la propriété des fichiers montés en bind peut devoir correspondre à la plage remappée. Voir la section Reprise.

3. Modèle d'exécution de conteneur durci

Démarrez avec un conteneur minimal et n'ajoutez que ce qui est nécessaire. Les options ci-dessous illustrent une posture par défaut robuste.

Exemple construit : conteneur web durci

# Image et digest d'exemple construit ; remplacez par les vôtres
IMAGE="mycorp/web:1.2.3@sha256: deadbeef..."

sudo docker network create --driver bridge --subnet 172.30.0.0/24 --internal app_net || true

sudo docker run --rm -d \
  --name web \
  --network app_net \
  --read-only \
  --tmpfs /tmp: rw, noexec, nosuid, nodev, size=64m \
  --pids-limit=200 \
  --memory=256m --memory-swap=256m --cpus=1.0 \
  --cap-drop=ALL --cap-add=NET_BIND_SERVICE \
  --security-opt no-new-privileges \
  --security-opt seccomp=default \
  --user 10001:10001 \
  -v /srv/web/static:/srv/web/static: ro \
  -p 127.0.0.1:8080:8080 \
  --health-cmd='curl -sSf http://127.0.0.1:8080/health || exit 1' \
  --health-interval=30s --health-retries=3 \
  "$IMAGE"

Pourquoi ces choix :

  • --read-only empêche les écritures inattendues dans le rootfs.
  • --tmpfs /tmp:... fournit une zone d'écriture sûre pour les fichiers temporaires.
  • --cap-drop=ALL puis --cap-add=NET_BIND_SERVICE appliquent le moindre privilège.
  • --security-opt no-new-privileges bloque l'escalade de privilèges.
  • --security-opt seccomp=default conserve le filtrage d'appels système par défaut.
  • --user 10001:10001 évite d'exécuter en root dans le conteneur.
  • -v ...:ro monte des données en lecture seule depuis l'hôte.
  • --network app_net --internal combiné à une publication locale sur 127.0.0.1 resserre l'exposition réseau.

Si l'application a besoin d'autres chemins en écriture que /tmp, montez-les explicitement avec les options adaptées au lieu de désactiver globalement --read-only.

4. Gestion des secrets

Objectifs : éviter d'inclure des secrets dans les images et les variables d'environnement ; préférer des fichiers avec des permissions restreintes.

  • Secrets Docker Swarm (exemple construit) :
echo "constructed-db-password" | docker secret create db_password -

docker service create \
  --name app \
  --secret source=db_password, target=/run/secrets/db_password, mode=0400 \
  "$IMAGE"
  • Conteneurs autonomes avec un fichier hôte (exemple construit) :
# Créer un fichier lisible uniquement par l'utilisateur in‑container (UID 10001 ici)
sudo install -o 10001 -g 10001 -m 0400 /root/db_password /srv/app/db_password

sudo docker run --rm \
  --user 10001:10001 \
  -v /srv/app/db_password:/run/secrets/db_password: ro \
  "$IMAGE"

Préférez le montage de fichiers aux variables d'environnement pour les secrets. Assurez l'UID/GID et le mode (0400/0440) corrects côté hôte.

5. Éviter les options et modes dangereux

  • N'utilisez --privileged qu'en ultime recours.
  • Évitez --pid=host, --net=host et --ipc=host dans le pilote ; ils élargissent le rayon d'impact.
  • Publiez les ports explicitement et liez à loopback si le service est local uniquement, par ex. -p 127.0.0.1:8080:8080.

6. Exposition distante de l'API

  • N'exposez jamais l'API Docker non authentifiée sur tcp://0.0.0.0:2375.
  • Si un accès distant est requis, activez TLS et appliquez des règles de pare-feu strictes.

Extrait daemon.json avec TLS (exemple construit) :

{
  "hosts": ["unix:///var/run/docker.sock", "tcp://0.0.0.0:2376"],
  "tls": true,
  "tlscert": "/etc/docker/certs.d/server-cert.pem",
  "tlskey": "/etc/docker/certs.d/server-key.pem",
  "tlscacert": "/etc/docker/certs.d/ca.pem",
  "tlsverify": true
}

7. Hygiène d'images et épinglage

  • Épinglez les images par digest : repo: tag@sha256:<digest> pour garantir la reproductibilité.
  • Gardez les images de base minimales et supprimez les paquets inutiles dans vos images.
  • Optionnellement, définissez DOCKER_CONTENT_TRUST=1 lors des pulls et exécutions si la confiance de contenu est configurée.

8. Limites de ressources

  • Limites mémoire et CPU : --memory, --memory-swap, --cpus.
  • Limite de nombre de processus : --pids-limit. Elles empêchent un conteneur d'épuiser les ressources de l'hôte.

---

Carte rapide des contrôles

Le tableau indique où définir chaque contrôle et le résultat attendu.

DomaineContrôleOù le définirExempleRésultat attendu
AccèsRestreindre le groupe dockerHôteGérer l'appartenance via sudoMoins d'accès équivalents root
Démonuserns-remap/etc/docker/daemon.json"userns-remap": "default"Root conteneur mappé vers UID non privilégié
ExécutionUtilisateur non-rootDockerfile/options run--user 10001:10001Application sans droits root
ExécutionCapacitésOptions run--cap-drop=ALL --cap-add=NET_BIND_SERVICEMoindre privilège
ExécutionNo new privilegesOptions run--security-opt no-new-privilegesBloque l'escalade
ExécutionProfil seccompOptions run--security-opt seccomp=defaultFiltrage d'appels système
FSRoot en lecture seuleOptions run--read-only; --tmpfs /tmpÉcritures inattendues bloquées
SecretsFichiers montés ou SwarmOptions run/service-v secret:...:ro; --secret ...Pas de secrets en env ou image
RéseauExposition minimaleOptions run/réseaux-p 127.0.0.1:8080:8080; --internalSurface d'attaque réduite
LimitesMémoire/CPU/PIDsOptions run--memory 256m --cpus 1 --pids-limit 200Usage contenu

---

Vérification et diagnostics

Vérifiez chaque contrôle avec des tests ciblés et enregistrez les résultats.

  1. Paramètres du démon
sudo docker info | grep -E "userns|Live Restore|Security Options"
cat /etc/docker/daemon.json

Attendu : userns activé, Live Restore à true, et des options de sécurité (apparmor/selinux si applicable).

  1. Options d'exécution du conteneur (nom d'exemple : web)
sudo docker inspect web \
  --format 'User={{.Config.User}} ReadonlyRootfs={{.HostConfig.ReadonlyRootfs}} CapAdd={{.HostConfig.CapAdd}} CapDrop={{.HostConfig.CapDrop}} NoNewPrivs={{.HostConfig.SecurityOpt}} PidsLimit={{.HostConfig.PidsLimit}} Memory={{.HostConfig.Memory}} CPUs={{.HostConfig.NanoCpus}}'

Attendu : un UID non-root, rootfs en lecture seule, CapDrop avec ALL, NoNewPrivs présent, limites non nulles.

  1. Contrôles dans le conteneur
sudo docker exec web id -u
sudo docker exec web sh -c 'test -w / || echo "rootfs is read-only"'
sudo docker exec web sh -c 'grep NoNewPrivs /proc/1/status || true'
sudo docker exec web sh -c 'grep CapEff /proc/1/status'

Attendu : UID différent de 0, rootfs non inscriptible, ligne NoNewPrivs: 1 (selon noyau), masque de capacités réduit.

  1. Secrets
sudo docker exec web sh -c 'stat -c "%U %G %a %n" /run/secrets/* 2>/dev/null || true'

Attendu : des fichiers de secret existent seulement si montés ; modes 400/440 et possession par l'utilisateur applicatif.

  1. Exposition réseau
sudo ss -ltnp | grep 8080
sudo docker network inspect app_net | grep -E '"Internal":|"Subnet"'

Attendu : service à l'écoute sur 127.0.0.1:8080, réseau avec "Internal": true.

  1. Santé et journaux
sudo docker ps --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}'
sudo docker logs --tail 50 web

Attendu : conteneur healthy, pas d'erreurs de permission dues au rootfs read-only ou aux capacités manquantes.

---

Modes d'échec et reprise

Lors du durcissement, les problèmes les plus fréquents concernent les permissions, les capacités et les chemins en écriture. Prévoyez un plan de rollback simple.

Problèmes courants

SymptômeCause probableCorrectif rapide
L'application ne peut pas écrire--read-only bloque les écrituresMonter un chemin en écriture : -v /srv/app/data:/app/data: rw ou --tmpfs /var/cache/app
Permission refusée sur un bind mountuserns-remap change le mapping UID/GIDchown -R <uid_remappé>:<gid_remappé> /srv/app/data (exemple construit : 165536:165536)
Impossible d'écouter sur le port 80 en non-rootCapacité NET_BIND_SERVICE manquanteAjouter --cap-add=NET_BIND_SERVICE ou utiliser un port >= 1024
Appels réseau échouent depuis un réseau interne--internal bloque l'egressUtiliser un réseau non‑interne pour les services ayant besoin d'egress
Fichier secret illisibleUID/GID ou mode incorrect côté hôteDéfinir -o <uid> -g <gid> -m 0400 et monter :ro
Processus tué (OOM ou PIDs)Limites trop strictesAugmenter --memory ou --pids-limit avec marge mesurée

Spécificités du remap d'espace de noms utilisateur

Après userns-remap, root du conteneur correspond à une plage non privilégiée sur l'hôte, souvent à partir d'un UID élevé comme 165536 (exemple construit). Les fichiers montés depuis l'hôte doivent être possédés par cet UID/GID pour être inscriptibles dans le conteneur.

Correctif d'exemple pour un montage inscriptible :

# Déterminer l'UID/GID remappés (exemple construit suppose 165536)
grep dockremap /etc/subuid /etc/subgid || true

# Ajuster la propriété d'un répertoire de données
sudo chown -R 165536:165536 /srv/app/data

Revenir en arrière sur les changements du démon

  1. Restaurer le précédent daemon.json
sudo cp /etc/docker/daemon.json.bak.$(date +%F -d 'yesterday' 2>/dev/null || echo backup) /etc/docker/daemon.json
  1. Redémarrer le démon
sudo systemctl restart docker

Note : le redémarrage de Docker peut arrêter des conteneurs sauf si live-restore est activé. Prévoyez un créneau de maintenance pour la production.

Revenir en arrière sur l'exécution d'un conteneur

  • Arrêter le conteneur durci et le relancer avec les options antérieures connues.
sudo docker stop web
# Relance temporaire sans rootfs read-only et avec des capacités plus larges (exemple construit)
sudo docker run -d --name web --cap-drop=NET_RAW -p 127.0.0.1:8080:8080 "$IMAGE"

Documentez les différences et réappliquez le durcissement étape par étape pour isoler l'option fautive.

---

Liste de contrôle d'exploitation

Utilisez cette checklist concise pour chaque service durci. Cochez N/A si non applicable.

Accès et démon

  • [ ] Revoir l'appartenance au groupe docker ; retirer les comptes inutiles
  • [ ] Confirmer userns-remap activé et opérationnel
  • [ ] Activer live-restore et des options de logs bornées

Image et exécution

  • [ ] Épingler l'image par digest
  • [ ] Exécuter sous un utilisateur non-root
  • [ ] Supprimer toutes les capacités ; ne rajouter que le nécessaire
  • [ ] Activer no-new-privileges
  • [ ] Utiliser le profil seccomp par défaut ou plus strict

Système de fichiers et secrets

  • [ ] Activer --read-only
  • [ ] Fournir les zones d'écriture via --tmpfs ou montages dédiés
  • [ ] Monter les secrets en lecture seule avec UID/GID et mode corrects

Réseau et exposition

  • [ ] Utiliser des réseaux définis par l'utilisateur ; internes si possible
  • [ ] Lier les ports publiés à loopback sauf besoin externe
  • [ ] Garder l'API Docker distante désactivée ; sinon TLS et pare-feu

Limites et santé

  • [ ] Définir des limites mémoire, CPU et PIDs
  • [ ] Configurer un healthcheck si pertinent

Vérification

  • [ ] Capturer les champs clés de docker inspect du service
  • [ ] Valider dans le conteneur : non-root, rootfs read-only, capacités limitées
  • [ ] Confirmer la possession et les permissions des secrets
  • [ ] Confirmer que seuls les ports requis sont à l'écoute

Reprise

  • [ ] Sauvegarder daemon.json avant les changements
  • [ ] Documenter les anciennes options run pour un rollback rapide

---

Conclusion

Vous disposez désormais d'un chemin mesuré et reproductible pour durcir Docker dans des conditions proches de la production : établir une base de référence, appliquer des contrôles par couches (accès, exécution, système de fichiers, secrets, réseau, limites), puis vérifier chaque résultat avec des tests ciblés. Commencez par un pilote étroit que vous pouvez inspecter localement, consignez ce qui fonctionne et étendez progressivement la même approche aux autres services. Gardez les instructions de rollback à portée de main pour aller vite sans rester bloqué si quelque chose se casse.

Score de qualité de l’article

Utilité pour le lecteur 97%
  • check_circle Guide prêt à lire
  • check_circle Exemples pratiques inclus
  • check_circle URL d’article optimisée pour le SEO