Des images 10× plus petites, 0 CVE : Chainguard change la donne
Publié le :

Les images conteneurs traditionnelles transportent un héritage encombrant : shells, gestionnaires de paquets, outils de debugging. Cette complexité augmente la surface d’attaque et multiplie les vulnérabilités CVE. Face à ce constat, je me concentre ici sur les images Chainguard face aux images standards, pour un usage Kubernetes en production.
Problématique : images standards lourdes et risquées
Une image Debian ou Ubuntu standard contient des centaines de paquets dont la plupart ne servent jamais à l’exécution de votre application :
# Image traditionnelle nginx:latest : compter les paquets installésdocker run --rm nginx:latest dpkg -l | wc -l# ~150 paquets listés (exemple)Conséquences directes :
- Surface d’attaque élargie : chaque paquet est une porte d’entrée potentielle
- CVE en cascade : vulnérabilités dans des dépendances inutilisées
- Taille excessive : images de plusieurs centaines de Mo
- Temps de chargement long : plus de packages = plus de temps de chargement
# Taille de l'image nginx:latestdocker images nginx:latestREPOSITORY TAG IMAGE ID CREATED SIZEnginx latest 60adc2e137e7 2 weeks ago 152MBOn va utiliser trivy pour scanner les
vulnérabilités CVE critiques dans l’image nginx:latest :
# Scanner les vulnérabilités avec Trivytrivy image --table-mode summary --severity HIGH,CRITICAL nginx:latest2025-12-06T09:43:55Z INFO [vuln] Vulnerability scanning is enabled2025-12-06T09:43:55Z INFO [secret] Secret scanning is enabled2025-12-06T09:43:55Z INFO [secret] If your scanning is slow, please try '--scanners vuln' to disable secret scanning2025-12-06T09:43:55Z INFO [secret] Please see https://trivy.dev/docs/v0.68/guide/scanner/secret#recommendation for faster secret detection2025-12-06T09:43:55Z INFO Detected OS family="debian" version="13.2"2025-12-06T09:43:55Z INFO [debian] Detecting vulnerabilities... os_version="13" pkg_num=1502025-12-06T09:43:55Z INFO Number of language-specific files num=02025-12-06T09:43:55Z WARN Using severities from other vendors for some vulnerabilities. Read https://trivy.dev/docs/v0.68/guide/scanner/vulnerability#severity-selection for details.
Report Summary
┌────────────────────────────┬────────┬─────────────────┬─────────┐│ Target │ Type │ Vulnerabilities │ Secrets │├────────────────────────────┼────────┼─────────────────┼─────────┤│ nginx:latest (debian 13.2) │ debian │ 4 │ - │└────────────────────────────┴────────┴─────────────────┴─────────┘Le build maison : une fausse bonne idée
Pour réduire la taille et les CVE, certains optent pour des images minimalistes faites maison (Alpine, Scratch, distroless). Mais cette approche complexifie la maintenance. Je dois en effet :
- Maintenir des Dockerfiles de base et leurs mises à jour
- Traiter les CVE: patch, rebuild, re-scan, repush
- Gestion des dépendances système (apt/yum), pins et backports
- Pipeline et cache d’images à entretenir (temps opérateur)
Une solution : les images Chainguard
Les images Chainguard annonce réduire drastiquement ces risques pour vous :
En production, je vise trois gains concrets :
- réduire la surface d’attaque (moins de paquets = moins de CVE),
- accélérer mes pipelines (images plus petites, caches plus stables),
- renforcer la traçabilité (SBOM + attestations de provenance vérifiables).
Architecture et principes (cloud‑native)
Les images Chainguard sont conçues spécifiquement pour les conteneurs et répondent aux limites des images standards.
Principes de conception :
- ✅ SBOM natif : Software Bill of Materials généré automatiquement
- ✅ Rootless par défaut : principe de moindre privilège
- ✅ Mises à jour rapides : patchs CVE en moins de 24h
- ✅ APK package manager : emprunté à Alpine Linux
- ✅ glibc moderne : compatibilité maximale
- ✅ Provenance attestations : signatures cryptographiques SLSA
Concrètement, je ne change pas mon code : je remplace l’OS utilisateur par un runtime minimal sans shell par défaut, et j’obtiens un SBOM généré à la construction. Moins de « dépendances fantômes », des patchs plus prévisibles.
Images Chainguard disponibles
Chainguard propose +1000 images maintenues. Voici une liste incomplète de ce qu’on peut y trouver:
- Base / Systèmes minimaux
- static
- busybox
- glibc-dynamic
- glibc-static
- wolfi-base
- gcc-glibc
- Langages / Runtimes
- python
- node
- ruby
- go
- jdk
- jre
- Outils de build / Toolchains
- apko
- melange
- bazel
- cmake
- cargo
- rust
- CLI & Outils DevOps
- kubectl
- helm
- kustomize
- skopeo
- cosign
- gh
- Serveurs & Middleware
- nginx
- caddy
- haproxy
- redis
- postgres
- prometheus
- Sécurité / Observabilité
- falco
- trivy
- tetragon
- vector
- node-exporter
- promtail
- Images pour Kubernetes
- pause
- coredns
- etcd
- kube-apiserver
- kube-controller-manager
- kube-proxy
Source: Directory Chainguard ↗ (pagination et filtres par catégories disponibles).
Mise en pratique des images Chainguard : exemple Python
Pour illustrer, je crée une simple application Python Flask. Voici le Dockerfile utilisant une image Chainguard Python.
# Build sur image Chainguard -dev (avec shell et tools)FROM cgr.dev/chainguard/python:latest-dev AS builderWORKDIR /appCOPY requirements.txt .RUN pip install --no-cache-dir -r requirements.txt
# Runtime images ChainguardFROM cgr.dev/chainguard/python:latestWORKDIR /appCOPY --from=builder /app/.local /app/.localCOPY app.py .ENV PATH="/app/.local/bin:$PATH"ENTRYPOINT ["python", "app.py"]Le modèle économique Chainguard
Selon la page Pricing ↗, les offres sont structurées en trois volets pour les Chainguard Containers :
- Free Images : un sous-ensemble d’images gratuites (tags
:latest), idéal pour tests/déploiements limités. - Per Image : licence par image (base, application, AI/ML, FIPS), avec CVE Remediation SLA contractuel et accès à tous les tags supportés en amont (ex: Python 3.10–3.14).
- Catalog : accès complet au catalogue (~1700+ images), licence basée sur la taille de l’organisation, avec SLA CVE, demandes de nouvelles images/paquets et EOL grace period.
Je compare les coûts récurrents d’un build maison à l’abonnement Per Image/Catalog :
- Maintenance Dockerfiles (temps ingénieur), suivi OS/dépendances, rebuild/retest → récurrence mensuelle.
- SLA CVE interne: détection/patch sous 7–14 jours, coordination, re-scan, publication → effort soutenu et risqué.
- SBOM + provenance: outillage, intégration CI, stockage, audit.
- Images FIPS/STIG: coûts élevés de durcissement et conformité.
Constat pragmatique: pour des équipes qui gèrent plusieurs images et des exigences compliance (FIPS/STIG), l’abonnement Per Image ou Catalog remplace une part importante de la charge opérationnelle et réduit le temps de remédiation. Le Free tier suffit pour POC et tests, pas pour un parc production hétérogène avec SLA.
Allez on vérifie tout ça !
Je compare rapidement nginx:latest (standard) et
cgr.dev/chainguard/nginx:latest.
On compare la taille, le nombre de paquets et les CVE.
# On récupère l'image chainguard NGINXdocker pull cgr.dev/chainguard/nginx:latest
# Comparer rapidement les taillesdocker images | grep -E "(^REPOSITORY|nginx\s|chainguard/nginx)"REPOSITORY TAG IMAGE ID CREATED SIZEnginx latest 60adc2e137e7 2 weeks ago 152MBcgr.dev/chainguard/nginx latest 24a61bdb908b 2 weeks ago 16.9MBOn va utiliser syft pour lister les paquets et trivy pour scanner les CVE.
Le nombre de paquets installés :
# Image standard : lister les paquets installés (dpkg)syft nginx:latest -q | wc -l152# Image Chainguard : pas de shell/dpkg → passer par le SBOMsyft cgr.dev/chainguard/nginx:latest -q | wc -l16Les vulnérabilités CVE critiques :
# Avec Trivytrivy image --severity HIGH,CRITICAL cgr.dev/chainguard/nginx:latest2025-12-06T09:51:06Z INFO [vuln] Vulnerability scanning is enabled2025-12-06T09:51:06Z INFO [secret] Secret scanning is enabled2025-12-06T09:51:06Z INFO [secret] If your scanning is slow, please try '--scanners vuln' to disable secret scanning2025-12-06T09:51:06Z INFO [secret] Please see https://trivy.dev/docs/v0.68/guide/scanner/secret#recommendation for faster secret detection2025-12-06T09:51:06Z INFO Detected OS family="wolfi" version="20230201"2025-12-06T09:51:06Z INFO [wolfi] Detecting vulnerabilities... pkg_num=152025-12-06T09:51:06Z INFO Number of language-specific files num=0
Report Summary
┌──────────────────────────────────────────────────┬───────┬─────────────────┬─────────┐│ Target │ Type │ Vulnerabilities │ Secrets │├──────────────────────────────────────────────────┼───────┼─────────────────┼─────────┤│ cgr.dev/chainguard/nginx:latest (wolfi 20230201) │ wolfi │ 0 │ - │└──────────────────────────────────────────────────┴───────┴─────────────────┴─────────┘Attendus observés en pratique : moins de dépendances, moins de CVE et des tailles réduites côté images Chainguard.
On vérifie l’image python de la même façon :
# Image Python Chainguarddocker pull cgr.dev/chainguard/python:latestdocker pull python:3.11-slim
# Comparer les taillesdocker images | grep pythoncgr.dev/chainguard/python latest 95ca247652bb 3 days ago 64MBpython 3.11-slim 040af88f5bce 2 weeks ago 124MB# Vérifier les CVEtrivy image --table-mode summary --severity HIGH,CRITICAL python:3.11-slim2025-12-06T10:01:58Z INFO [vuln] Vulnerability scanning is enabled2025-12-06T10:01:58Z INFO [secret] Secret scanning is enabled2025-12-06T10:01:58Z INFO [secret] If your scanning is slow, please try '--scanners vuln' to disable secret scanning2025-12-06T10:01:58Z INFO [secret] Please see https://trivy.dev/docs/v0.68/guide/scanner/secret#recommendation for faster secret detection2025-12-06T10:01:58Z INFO Detected OS family="debian" version="13.2"2025-12-06T10:01:58Z INFO [debian] Detecting vulnerabilities... os_version="13" pkg_num=872025-12-06T10:01:58Z INFO Number of language-specific files num=12025-12-06T10:01:58Z INFO [python-pkg] Detecting vulnerabilities...2025-12-06T10:01:58Z WARN Using severities from other vendors for some vulnerabilities. Read https://trivy.dev/docs/v0.68/guide/scanner/vulnerability#severity-selection for details.
Report Summary
┌──────────────────────────────────────────────────────────────────────────────────┬────────────┬─────────────────┬─────────┐│ Target │ Type │ Vulnerabilities │ Secrets │├──────────────────────────────────────────────────────────────────────────────────┼────────────┼─────────────────┼─────────┤│ python:3.11-slim (debian 13.2) │ debian │ 0 │ - │├──────────────────────────────────────────────────────────────────────────────────┼────────────┼─────────────────┼─────────┤trivy image --severity HIGH,CRITICAL cgr.dev/chainguard/python:latest2025-12-06T09:57:43Z INFO [vuln] Vulnerability scanning is enabled2025-12-06T09:57:43Z INFO [secret] Secret scanning is enabled2025-12-06T09:57:43Z INFO [secret] If your scanning is slow, please try '--scanners vuln' to disable secret scanning2025-12-06T09:57:43Z INFO [secret] Please see https://trivy.dev/docs/v0.68/guide/scanner/secret#recommendation for faster secret detection2025-12-06T09:57:43Z INFO Detected OS family="wolfi" version="20230201"2025-12-06T09:57:43Z INFO [wolfi] Detecting vulnerabilities... pkg_num=252025-12-06T09:57:43Z INFO Number of language-specific files num=0
Report Summary
┌───────────────────────────────────────────────────┬───────┬─────────────────┬─────────┐│ Target │ Type │ Vulnerabilities │ Secrets │├───────────────────────────────────────────────────┼───────┼─────────────────┼─────────┤│ cgr.dev/chainguard/python:latest (wolfi 20230201) │ wolfi │ 0 │ - │└───────────────────────────────────────────────────┴───────┴─────────────────┴─────────┘On voit le même pattern : l’image Chainguard est plus légère et sans CVE critiques.
Rootless ou pas ?
La plupart des images Chainguard sont conçues pour tourner en non-root par défaut. Je vérifie avec une simple inspection :
# Vérifier l'UID par défautdocker inspect cgr.dev/chainguard/nginx:latest --format='{{.Config.User}}'65532L’UID 65532 correspond à l’utilisateur nobody dans l’image. Je m’assure
donc que mes manifests Kubernetes utilisent des ports non privilégiés (>=1024)
et runAsNonRoot: true dans les securityContext.
Vérification de la présence du SBOM
Chaque image Chainguard intègre un SBOM natif. Je peux l’extraire avec
syft :
# Extraire le SBOMsyft cgr.dev/chainguard/nginx:latest -o table ✔ Loaded image cgr.dev/chainguard/nginx:latest ✔ Parsed image sha256:24a61bdb908b89d99d4a61dc5383a3d592c07a1f9ef9e94b5fdf1dfc5e7adf48 ✔ Cataloged contents 7f9cbf3d9edd78424a9a5097be27318b8d3545319e156d54e71f278043044dc2 ├── ✔ Packages [15 packages] ├── ✔ File metadata [91 locations] ├── ✔ Executables [26 executables] └── ✔ File digests [91 files]NAME VERSION TYPEca-certificates-bundle 20251003-r0 apkglibc 2.42-r4 apkglibc-locale-posix 2.42-r4 apkld-linux 2.42-r4 apklibcrypt1 2.42-r4 apklibcrypto3 3.6.0-r4 apklibgcc 15.2.0-r6 apklibpcre2-8-0 10.47-r0 apklibssl3 3.6.0-r4 apklibxcrypt 4.5.2-r0 apknginx-mainline 1.29.3-r0 apknginx-mainline-config 1.29.3-r0 apknginx-mainline-package-config 1.29.3-r0 apkwolfi-baselayout 20230201-r24 apkzlib 1.3.1-r51 apkLe SBOM liste les 15 paquets APK installés, bien moins qu’une image standard.
Signatures & provenance (adoption production)
Avant de basculer une image en production, on vérifie systématiquement les signatures et l’attestation de provenance. Objectif : m’assurer que le contenu provient d’une chaîne de build de confiance.
# Vérifier la signature Sigstore (cléless) avec issuer + identité attenduecosign verify \ --certificate-oidc-issuer https://token.actions.githubusercontent.com \ --certificate-identity-regexp 'https://github.com/chainguard-images/.*' \ cgr.dev/chainguard/nginx:latestConclusion
En production, je veux réduire les risques tout en gagnant du temps. Les images standards cachent des coûts en CVE, taille et maintenance. À l’inverse, les images Chainguard me donnent un SBOM natif, des attestations de provenance, et un SLA de remédiation CVE qui stabilise mon runbook sécurité.
Quand basculer vers Chainguard ?
- Je gère plusieurs images et je veux un SLA CVE (7–14 jours) sans charge opérationnelle lourde.
- J’ai des exigences compliance (FIPS/STIG) ou des audits récurrents.
- Je veux des images rootless par défaut et un catalogue large.
Méthode pragmatique (résumé en 4 étapes):
- Remplacer l’image de base par sa variante Chainguard (NGINX, Python, etc.).
- Vérifier rootless et ajuster
securityContext(ports >=1024). - Intégrer SBOM + scan (syft/trivy) et cosign verify en CI.
- Pinner par digest (
@sha256:...) pour éliminer les tags mutables.
Ma règle simple: j’utilise Chainguard pour les environnements durables et audités; je garde des images standards pour des usages éphémères/POC ou des tests ciblés. Ainsi, je diminue la surface d’attaque sans ralentir mes déploiements.