Docker Hardened Images : la sécurité des conteneurs repensée

Docker a lancé en 2026 le programme ‘Docker Hardened Images’ (DHI), une collection d’images officielles prédurcies, auditées par des équipes de sécurité, et fournies avec des attestations de provenance (SLSA Level 3). Ces images sont disponibles dans Docker Hub et constituent le nouveau standard de référence pour les images de base en production.

Avant DHI, choisir une image de base sécurisée nécessitait de naviguer entre des options imparfaites : les images officielles Docker Hub (souvent volumineuses avec des packages inutiles), les images Alpine (légères mais avec des incompatibilités libc), et les images distroless de Google (sécurisées mais difficiles à déboguer). DHI consolide les meilleures pratiques de chacune dans une gamme cohérente.

Ce guide couvre l’utilisation des Docker Hardened Images, les techniques de durcissement de vos propres Dockerfiles, l’intégration de Docker Scout pour le scan de vulnérabilités, et les politiques d’images à implémenter pour les environnements de production.

Anatomie d’une image Docker sécurisée

Une image Docker sécurisée repose sur quatre principes. Minimalité : n’inclure que les packages strictement nécessaires à l’exécution de l’application. Une image Python qui fait tourner une API REST n’a pas besoin d’un shell bash, de curl, ou de compilateurs, les images distroless ou DHI les excluent par défaut.

Utilisateur non-root : l’application s’exécute sous un utilisateur dédié avec des privilèges minimaux. Si un attaquant exploite une vulnérabilité dans l’application, il obtient les droits de cet utilisateur restreint, pas de root. Configurez toujours un `USER appuser` dans votre Dockerfile avant l’instruction `CMD`.

Filesystem en lecture seule : le container ne peut pas modifier son filesystem runtime (sauf les volumes explicitement montés). Cela prévient de nombreuses attaques qui nécessitent d’écrire des fichiers sur le disque (scripts de persistence, shells dropped). Configurez `securityContext.readOnlyRootFilesystem: true` dans votre manifeste Kubernetes.

Multi-stage builds : réduire la surface d’attaque

Le multi-stage build est la technique la plus efficace pour réduire la taille et la surface d’attaque des images de production. Le principe : utiliser une image complète (avec compilateurs, outils de build) pour construire l’application, puis copier UNIQUEMENT les artefacts de build dans une image minimale pour la production.

Pour une application Python, le stage de build installe les dépendances et compile les wheels. Le stage de production utilise une image Python distroless ou DHI, copie les wheels et le code source, et ne contient aucun outil d’installation. Résultat : une image de production 5 à 10 fois plus petite, sans pip, sans gcc, sans bash.

Pour les applications Go, le multi-stage est encore plus radical : le binaire compilé (statically linked) peut être copié dans `FROM scratch`, une image absolument vide. Le container résultant ne contient que votre binaire et rien d’autre. Aucun shell, aucune libc, aucune surface d’attaque au niveau OS.

Docker Scout : scan de vulnérabilités et CVEs

Docker Scout est l’outil de scan de vulnérabilités intégré à Docker Desktop et Docker Hub. Il analyse l’image layer par layer, identifie tous les packages installés, et les compare avec des bases de CVEs (NVD, GitHub Advisory, OSS-Fuzz). Disponible gratuitement pour les repositories publics et en tier payant pour les private registries.

Intégrez Docker Scout dans votre CI/CD : `docker scout cves monimage:tag –exit-code` retourne un code non-zéro si des CVEs critiques ou élevées sont détectées. Cela bloque automatiquement le push d’une image vulnérable vers votre registry de production. Configurez des exceptions pour les CVEs sans patch disponible (avec une justification documentée et une date de révision).

Au-delà des CVEs, Docker Scout analyse les politiques de sécurité configurées (pas d’image root, pas de latest tag, provenance attestation présente) et vous donne un score de conformité. Ce score peut être intégré dans des gates de déploiement : une image en dessous d’un score minimum est bloquée par ArgoCD ou votre pipeline CI.

Politiques d’images en production : OPA et Kyverno

Une politique d’images cohérente et automatiquement enforçée est la clé de la sécurité des conteneurs en production. Des outils comme Kyverno (natif Kubernetes, YAML) ou OPA Gatekeeper permettent de définir des règles qui bloquent le déploiement de pods qui ne respectent pas vos standards.

Règles Kyverno essentielles : interdire les images avec le tag `:latest` (oblige des tags de version immuables), interdire les conteneurs qui s’exécutent en root (uid 0), interdire les conteneurs en mode privilégié (`privileged: true`), et interdire les images provenant de registries non approuvés (seuls votre registry interne et Docker Hub DHI sont autorisés).

Ces politiques s’appliquent en mode audit (log seulement) ou enforce (bloque le pod). Commencez en mode audit pour identifier les violations existantes sans casser la production, corriger les violations, puis passez en enforce. La transition audit → enforce sur un cluster existant prend typiquement 2 à 4 semaines.

Gestion du cycle de vie des images et mise à jour

Les images Docker ont un cycle de vie : une image sécurisée aujourd’hui peut contenir des CVEs demain suite à la découverte de nouvelles vulnérabilités dans ses composants. Sans processus de mise à jour des images de base, votre sécurité se dégrade silencieusement.

Automatisez la mise à jour des images de base avec Renovate Bot ou Dependabot. Ces outils détectent les nouvelles versions de vos images FROM (ex: `python:3.12-slim → python:3.13-slim`) et créent automatiquement des pull requests. Configurez un cycle de mise à jour hebdomadaire pour les images de base, les vulnérabilités critiques dans les images populaires sont patchées rapidement.

Implémentez une politique de rétention des images dans votre registry : supprimez les images non déployées après 30 jours, et les images en production après 90 jours de non-utilisation. Un registry non entretenu accumule des milliers d’images obsolètes, augmentant les coûts de stockage et compliquant l’audit de sécurité. Harbour, Artifactory, et AWS ECR proposent tous des politiques de lifecycle automatisées.

# Dockerfile sécurisé avec multi-stage build
# ─── Stage 1 : Build ───────────────────────────────────────────────
FROM python:3.12-slim AS builder

WORKDIR /app
COPY requirements.txt .

# Installer dans un venv isolé (pas de pollution de l'image base)
RUN python -m venv /opt/venv &&     /opt/venv/bin/pip install --no-cache-dir --upgrade pip &&     /opt/venv/bin/pip install --no-cache-dir -r requirements.txt

# ─── Stage 2 : Production (Docker Hardened Image) ──────────────────
FROM python:3.12-slim AS production

# Métadonnées et provenance
LABEL org.opencontainers.image.source="https://github.com/monorg/monapp"
LABEL org.opencontainers.image.vendor="MonOrg"

# Utilisateur non-root dédié
RUN groupadd -r appgroup && useradd -r -g appgroup -u 1000 appuser

WORKDIR /app

# Copier UNIQUEMENT le venv et le code source (pas pip, pas compilateurs)
COPY --from=builder /opt/venv /opt/venv
COPY --chown=appuser:appgroup src/ ./src/

# Droits minimaux sur les fichiers
RUN chmod -R 550 /app && chmod -R 550 /opt/venv

USER appuser

ENV PATH="/opt/venv/bin:$PATH"
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1

EXPOSE 8080

# Healthcheck intégré
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3     CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8080/health')" || exit 1

CMD ["python", "-m", "uvicorn", "src.main:app", "--host", "0.0.0.0", "--port", "8080"]

# Pour Kubernetes, ajoutez dans le securityContext du pod :
# securityContext:
#   readOnlyRootFilesystem: true
#   allowPrivilegeEscalation: false
#   runAsNonRoot: true
#   runAsUser: 1000

Distroless et rootless : pousser le durcissement au bout

Une image durcie ne contient que le strict necessaire a l’execution. Les images distroless poussent cette logique a l’extreme : pas de shell, pas de gestionnaire de paquets, pas d’utilitaires – juste le binaire et ses dependances. La surface d’attaque s’effondre, car un attaquant qui obtiendrait un acces ne trouve aucun outil pour rebondir. Combinee a une execution rootless (l’application ne tourne pas en root dans le conteneur), cette approche neutralise une grande partie des techniques d’escalade.

Le cout est une legere perte de confort de debug, qu’on compense en deboguant sur une image de developpement separee. En production, le minimalisme est une fonctionnalite de securite, pas une contrainte.

# Build classique puis image distroless rootless
FROM golang:1.23 AS build
WORKDIR /src
COPY . .
RUN CGO_ENABLED=0 go build -o /app ./cmd/server

# Image finale : pas de shell, pas de root
FROM gcr.io/distroless/static:nonroot
COPY --from=build /app /app
USER nonroot:nonroot
ENTRYPOINT ["/app"]

Signer et verifier ses images : verrouiller la chaine logicielle

Une image durcie qui peut etre remplacee par une version malveillante ne sert a rien. La securite de la chaine d’approvisionnement impose de signer chaque image au moment du build et de verifier cette signature au deploiement. Cosign rend l’operation simple : on signe avec une cle (ou en mode keyless via OIDC), et le cluster refuse de demarrer toute image dont la signature ne correspond pas a une source de confiance.

Ce verrou repond a une menace concrete de 2026 : la compromission de registres et l’injection d’images piegees. Sans verification de signature, rien ne distingue votre image legitime d’une contrefacon poussee par un attaquant ayant obtenu un acces au registre.

# Signer une image au build
cosign sign --key cosign.key registry.example.com/app:1.4.0

# Verifier avant deploiement (a integrer en CI/CD)
cosign verify --key cosign.pub registry.example.com/app:1.4.0 
  || { echo "Signature invalide - deploiement refuse"; exit 1; }

Securite a l’execution : confiner le conteneur en production

Le durcissement de l’image protege au build ; la securite a l’execution protege pendant que le conteneur tourne. Meme une image impeccable peut etre detournee via une faille applicative. Les garde-fous runtime limitent alors les degats : systeme de fichiers en lecture seule, suppression de toutes les capabilities Linux non indispensables, interdiction de l’escalade de privileges et profil seccomp restreignant les appels systeme dangereux.

Ces options se declarent dans la specification du conteneur et coutent peu a activer. Elles transforment un conteneur compromis en bac a sable inerte : l’attaquant a beau executer du code, il ne peut ni ecrire sur le disque, ni acquerir de nouveaux droits, ni appeler les fonctions noyau sensibles.

# securityContext Kubernetes -- confinement a l'execution
securityContext:
  readOnlyRootFilesystem: true
  allowPrivilegeEscalation: false
  runAsNonRoot: true
  capabilities:
    drop: ["ALL"]            # aucune capability Linux par defaut
  seccompProfile:
    type: RuntimeDefault     # filtre les appels systeme dangereux

Sources et références

WP Admin Lab
· Chef de la rédaction

La rédaction de WP Admin Lab réunit des praticiens WordPress, sécurité et développement web. Nous publions des guides testés en conditions réelles, vérifiés en production et régulièrement mis à jour, sans jargon inutile. Chaque procédure est relue avant publication.