Tu déploies une image PHP 8.3 en production, tu sais vaguement qu'elle contient des CVE, mais aucun signal clair ne t'indique si la situation est critique ou simplement négligeable. Le 22 août 2024, Docker a annoncé en bêta publique les Health Scores : une notation alphabétique de A à F pour chaque image hébergée sur Docker Hub. Un coup d'œil suffit désormais pour évaluer la posture sécurité d'une image, sans dépouiller des centaines de vulnérabilités brutes. Ce tutoriel te montre comment lire ce score depuis la CLI, parser le rapport JSON Scout et, surtout, configurer un gate GitHub Actions qui bloque automatiquement tout merge si la note descend sous C.
Docker Scout Health Scores : une note alphabétique pour ta sécurité container
Les Health Scores sont le résultat d'une agrégation intelligente des données que Docker Scout collecte déjà sur chaque image poussée sur Docker Hub : inventaire des paquets, matching CVE, disponibilité des correctifs, fraîcheur de l'image de base. Plutôt qu'une liste brute de vulnérabilités, Docker produit une lettre unique — de A (excellente posture) à F (vulnérabilités critiques non corrigées présentes) — visible directement sur la page Docker Hub de l'image. La note est calculée à partir de plusieurs critères pondérés.
- Nombre de CVE par niveau de sévérité : Critical, High, Medium, Low
- Part des CVE fixables — corrigeables par un simple bump de la base image
- Fraîcheur de l'image de base par rapport aux releases officielles
- Présence d'une SBOM (Software Bill of Materials) dans les attestations de l'image
- Ratio CVE non-fixables vs fixables (une CVE sans patch upstream pèse moins lourd)
- Score CVSS agrégé des vulnérabilités présentes
En pratique, une image officielle php:8.3-fpm-alpine atteint généralement un A ou B grâce à la minimalité d'Alpine et aux mises à jour régulières des mainteneurs Docker. Une image maison construite sur ubuntu:20.04 non mise à jour depuis six mois décrochera facilement un D ou F. La note est visible sur Docker Hub sous l'onglet Tags de chaque repository, à condition que l'image ait été scannée par Scout — automatique pour les images publiques et pour les repositories privés liés à une organisation Docker avec Scout activé.
Prendre en main la CLI Scout
La CLI Docker Scout est intégrée à Docker Desktop depuis la version 4.17. Sur un serveur Linux ou dans une pipeline CI, elle s'installe en standalone. Avant de brancher GitHub Actions, valide que tout fonctionne en local — c'est aussi le meilleur moyen de comprendre ce que le gate CI va analyser.
# Vérifier que Scout est disponible
docker scout version
# Scanner une image locale ou distante
# La première commande donne un résumé rapide
docker scout quickview myrepo/myimage:latest
# Lister tous les CVE de l'image
docker scout cves myrepo/myimage:latest
# Filtrer uniquement Critical et High
docker scout cves --only-severity critical,high myrepo/myimage:latest
# Voir uniquement les CVE pour lesquels un fix existe
docker scout cves --only-fixed myrepo/myimage:latest
# Recommandations de mise à jour de la base image
docker scout recommendations myrepo/myimage:latestLa commande quickview produit un tableau de bord condensé qui résume les CVE par catégorie et identifie la base image utilisée. Lance-la en premier pour avoir une photographie rapide avant d'aller dans le détail.
✓ Pulled
✓ SBOM of image already cached, 312 packages indexed
Target │ myrepo/myimage:latest │ 0C 3H 12M 5L
digest │ sha256:d4f8a1... │
Base image│ php:8.3-fpm-bullseye │ 0C 2H 9M 4L
What's next:
Refresh base image to reduce 3H and 9M → docker scout recommendations myrepo/myimage:latest
Analyze full CVE list → docker scout cves myrepo/myimage:latestExtraire et parser le rapport JSON Scout
La CLI Scout peut produire un rapport JSON structuré, indispensable pour du parsing automatisé en pipeline. C'est ce format que tu vas utiliser pour trier les CVE critiques par score CVSS et décider quoi patcher en priorité.
# Générer le rapport JSON complet
docker scout cves --format json myrepo/myimage:latest > scout-report.json
# Vérifier la structure de haut niveau
jq 'keys' scout-report.json{
"imageId": "sha256:d4f8a1bc...",
"tags": ["myrepo/myimage:latest"],
"platform": "linux/amd64",
"vulnerabilities": [
{
"cvss": {
"score": 9.8,
"severity": "CRITICAL"
},
"cve": "CVE-2024-2511",
"packageName": "openssl",
"packageVersion": "3.1.4",
"fixedVersion": "3.1.5",
"description": "Unbounded memory growth with session handling in TLSv1.3"
}
],
"summary": {
"critical": 1,
"high": 3,
"medium": 12,
"low": 5
}
}# CVE Critical et High triées par score CVSS décroissant
jq '
.vulnerabilities
| map(select(.cvss.severity == "CRITICAL" or .cvss.severity == "HIGH"))
| sort_by(-.cvss.score)
| .[] | {
cve: .cve,
score: .cvss.score,
severity: .cvss.severity,
package: .packageName,
installed: .packageVersion,
fix: .fixedVersion
}
' scout-report.json
# Compter les CVE fixables uniquement
jq '[.vulnerabilities[] | select(.fixedVersion != null and .fixedVersion != "")] | length' scout-report.jsonVotre dette technique s'accumule ?
Demander un audit →Configurer le gate CI/CD dans GitHub Actions
Docker fournit une action officielle docker/scout-action@v1 qui s'intègre nativement dans GitHub Actions et peut poster un commentaire récapitulatif directement sur la pull request. Voici un workflow complet : build de l'image, push sur Docker Hub, puis scan Scout avec sortie non-nulle si des vulnérabilités hautes ou critiques sont détectées.
# .github/workflows/docker-security.yml
name: Docker Security Gate
on:
pull_request:
branches: [main]
push:
branches: [main]
env:
REGISTRY: docker.io
IMAGE_NAME: ${{ github.repository }}
jobs:
build-and-scan:
runs-on: ubuntu-latest
permissions:
contents: read
security-events: write
pull-requests: write
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=ref,event=pr
type=sha,prefix=sha-
- name: Build and push
id: build
uses: docker/build-push-action@v6
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Docker Scout - Scan CVE et gate
id: scout
uses: docker/scout-action@v1
with:
command: cves
image: ${{ steps.meta.outputs.tags }}
only-severities: critical,high
write-comment: true
github-token: ${{ secrets.GITHUB_TOKEN }}
exit-code: true
- name: Docker Scout - Quickview résumé
if: always()
uses: docker/scout-action@v1
with:
command: quickview
image: ${{ steps.meta.outputs.tags }}Le paramètre clé est exit-code: true : si Scout détecte au moins un CVE de sévérité critical ou high, le step retourne un code d'erreur non-nul et le job échoue. Couplé à une branch protection rule qui exige que ce job passe, le merge est automatiquement bloqué. Le paramètre write-comment: true génère un commentaire récapitulatif sur la PR avec le détail des CVE trouvées — le développeur sait exactement quoi corriger sans ouvrir Docker Hub. Le step quickview avec if: always() assure qu'on dispose du résumé même quand le gate a échoué.
Implémenter le seuil grade C : stratégie en deux temps
Un grade C sur les Health Scores correspond approximativement à la présence de CVE hautes sans correctif immédiat, mais sans critique non-patchable. Pour éviter de bloquer sur des CVE pour lesquelles personne ne peut rien faire (aucun fix upstream disponible), on applique une stratégie en deux temps : avertissement sur les HIGH, blocage strict uniquement sur les CRITICAL fixables.
# Step 1 : Warning sur les CVE High (ne bloque pas)
- name: Scout - Gate WARNING (High)
uses: docker/scout-action@v1
with:
command: cves
image: ${{ steps.meta.outputs.tags }}
only-severities: high
write-comment: true
github-token: ${{ secrets.GITHUB_TOKEN }}
exit-code: false # Signalement uniquement
# Step 2 : Blocage strict sur Critical fixables
- name: Scout - Gate BLOQUANT (Critical + fix disponible)
uses: docker/scout-action@v1
with:
command: cves
image: ${{ steps.meta.outputs.tags }}
only-severities: critical
only-fixed: true # Uniquement si un fix existe
write-comment: false
exit-code: true # Bloque le merge
# Step 3 : Comparaison avec la branche main pour détecter les régressions
- name: Scout - Régression vs main
if: github.event_name == 'pull_request'
uses: docker/scout-action@v1
with:
command: compare
image: ${{ steps.meta.outputs.tags }}
to: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:main
only-severities: critical,high
write-comment: true
github-token: ${{ secrets.GITHUB_TOKEN }}
exit-code: trueLa combinaison only-severities: critical + only-fixed: true est la règle la plus pragmatique pour un gate : elle ne bloque que lorsqu'une vulnérabilité critique dispose effectivement d'un correctif. Tu évites la situation classique où la pipeline reste rouge des semaines parce qu'un CVE critique est connu mais que le mainteneur du paquet n'a pas encore publié de fix. Le step de comparaison avec main est un bonus précieux : il détecte les régressions introduites par la PR, indépendamment du niveau absolu de vulnérabilités.
Investiguer et prioriser les CVE : commandes clés
Une fois la pipeline en place, le premier scan révèle souvent une liste conséquente de CVE sur l'image de base. Avant de patcher dans tous les sens, identifie les actions à plus fort impact.
# Quelles CVE pourrais-je éliminer immédiatement en changeant la base image ?
docker scout recommendations myrepo/myimage:latest
# Comparer deux versions pour mesurer une amélioration
docker scout compare \
myrepo/myimage:v1.2.0 \
--to myrepo/myimage:v1.3.0 \
--ignore-unchanged
# Scanner uniquement les CVE sans fix dispo (informationnel, pas bloquant)
docker scout cves --only-unfixed myrepo/myimage:latest
# Rapport JSON pour intégration dans un dashboard ou Slack webhook
docker scout cves --format json --only-severity critical myrepo/myimage:latest \
| jq '{total_critical: .summary.critical, packages: [.vulnerabilities[].packageName] | unique}'La commande recommendations est le point de départ idéal : elle indique directement quelle version de ta base image éliminerait le plus de CVE en un seul changement de ligne FROM. Pour les projets PHP, le gain est souvent spectaculaire. Passer de php:8.3-fpm-bullseye à php:8.3-fpm-alpine suffit fréquemment à faire monter une image de grade D à grade A, en éliminant des centaines de paquets système inutiles.
# Avant : Debian Bullseye — surface d'attaque maximale, souvent grade C/D
FROM php:8.3-fpm-bullseye
RUN apt-get update && apt-get install -y \
libpq-dev \
libicu-dev \
&& docker-php-ext-install pdo_pgsql intl
# Après : Alpine — grade A dans la majorité des cas
FROM php:8.3-fpm-alpine
RUN apk add --no-cache \
libpq-dev \
icu-dev \
icu-libs \
&& docker-php-ext-install pdo_pgsql intl \
# Nettoyer les headers et outils de build
&& rm -rf /var/cache/apk/*
# Bonne pratique complémentaire : utilisateur non-root
RUN addgroup -g 1000 app && adduser -u 1000 -G app -s /bin/sh -D app
USER appEn combinant un Dockerfile minimal, un gate GitHub Actions rigoureux et la commande recommendations lancée régulièrement, tu passes d'une gestion réactive des CVE à une posture proactive. Le Health Score n'est pas une fin en soi : c'est un tableau de bord qui rend visible ce qui était invisible, et qui te force à agir avant que l'accumulation de dette sécurité devienne une urgence de production.
Votre dette technique s'accumule ?
Audit complet en 10 jours. Recommandations priorisées et actionnables.