fr

Construire des images Docker distroless : anatomie, outils et comparatif

Scratch, Google Distroless, Chainguard, Ubuntu Chiseled, Docker Hardened Images — comment fonctionnent les images minimales, comment les construire, et laquelle choisir.

Cet article fait suite à Optimisation Docker pour sites Astro.js — Recherche complète. L’article précédent effleurait le sujet des images distroless ; celui-ci plonge en profondeur dans leurs principes de construction, les outils disponibles, et les tradeoffs concrets.


Pourquoi distroless ?

Une image Docker classique basée sur ubuntu:24.04 ou debian:12 contient des centaines de paquets : shells, gestionnaire de paquets, utilitaires système (curl, wget, find…), compilateurs, fichiers de documentation. La plupart ne servent jamais en production. Selon Sysdig (State of Cloud-Native Security, 2024), 87% des images en production contiennent des vulnérabilités de sévérité haute ou critique — et la majorité provient de ces composants inutilisés.

Le principe distroless est simple : si votre application n’en a pas besoin, ça ne devrait pas être dans le container. On ne parle pas juste de réduire la taille — on parle de supprimer la surface d’attaque. Pas de shell signifie qu’un attaquant qui exploite une faille dans votre application ne peut pas obtenir de session interactive. Pas de curl signifie qu’il ne peut pas télécharger d’outils. Pas de gestionnaire de paquets signifie qu’il ne peut rien installer.

C’est exactement la manière dont Google exécute ses containers en interne depuis des années — et c’est désormais accessible à tous.


Le spectre des images minimales

Avant de plonger dans chaque approche, il est utile de visualiser le spectre complet :

Plus grand ◄─────────────────────────────────────────────────► Plus petit

ubuntu:24.04   alpine:3.21   distroless/base   distroless/static   scratch
  ~77 MB          ~5 MB          ~20 MB              ~2 MB           0 B
 100+ pkgs      ~17 pkgs       glibc + certs     certs seulement    rien
  shell ✓        shell ✓        shell ✗            shell ✗          shell ✗
  apt ✓          apk ✓          rien               rien             rien

Cinq approches coexistent aujourd’hui pour construire des images minimales. Chacune a sa philosophie, ses outils, et ses tradeoffs.


1. scratch — le vide absolu

scratch n’est pas une image — c’est un mot-clé réservé par Docker qui indique un filesystem vide. Zéro octet. Rien. L’implémentation est dans le PR #8827 de Moby : quand le builder rencontre FROM scratch, il ne tire aucune image et commence avec un filesystem vide.

Ce que le kernel fournit quand même

Même avec scratch, quand un container démarre, le runtime OCI (runc) monte automatiquement :

  • /proc, /sys, /dev (avec /dev/shm, /dev/pts)
  • /etc/hostname, /etc/hosts, /etc/resolv.conf
  • Le fichier marqueur .dockerenv

Ce sont des filesystems de namespace gérés par le kernel — ils ne font pas partie des layers de l’image.

Ce que vous devez fournir

Tout le reste. Si votre application a besoin de certificats TLS pour des appels HTTPS, c’est à vous de les copier. Si elle a besoin de données de timezone, idem. Si vous voulez un utilisateur non-root, il faut créer /etc/passwd manuellement.

Quand utiliser scratch

scratch est idéal pour les binaires statiquement liés :

# Go — binaire 100% statique
FROM golang:1.23 AS builder
WORKDIR /app
COPY . .
RUN CGO_ENABLED=0 go build -ldflags="-s -w" -o /myapp

FROM scratch
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=builder /myapp /myapp
ENTRYPOINT ["/myapp"]
# Rust — compilation statique via musl
FROM rust:1.83 AS builder
RUN rustup target add x86_64-unknown-linux-musl
WORKDIR /app
COPY . .
RUN cargo build --release --target x86_64-unknown-linux-musl

FROM scratch
COPY --from=builder /app/target/x86_64-unknown-linux-musl/release/myapp /myapp
ENTRYPOINT ["/myapp"]

Quand ne pas utiliser scratch

Dès que votre binaire est dynamiquement lié (ce qui est le cas de Node.js, Python, Java, et de la plupart des applications C/C++), scratch ne fonctionnera pas — il manque glibc, libssl, et les autres bibliothèques partagées. C’est là qu’interviennent les vraies images distroless.

Pour un serveur HTTP statique sur scratch (BusyBox httpd, darkhttpd), voir la section “Dockerfiles de référence” de l’article précédent.


2. Google Distroless — Debian dégraissée par Bazel

Origine et philosophie

Le projet GoogleContainerTools/distroless est la première implémentation grand public du concept distroless. Les images sont servies depuis gcr.io/distroless/* (migré vers Google Artifact Registry en backend, mais le domaine gcr.io reste fonctionnel).

La philosophie : prendre des paquets Debian et n’en extraire que le strict nécessaire pour faire tourner un runtime donné. Le résultat ne contient ni shell, ni gestionnaire de paquets, ni utilitaires système.

Comment elles sont construites

C’est ici que ça devient intéressant. Google n’utilise ni Docker, ni apt-get pour construire ces images. Le build repose entièrement sur Bazel, le système de build de Google, avec deux rulesets spécialisés :

  1. rules_distroless — des règles Bazel qui remplacent les commandes système : au lieu de apt-get install, useradd, update-ca-certificates, tout est exprimé comme des règles Bazel déclaratives qui extraient des fichiers depuis des paquets .deb sans jamais les “installer”.

  2. rules_oci — le successor de rules_docker, qui construit des images OCI sans démon Docker. Les layers sont assemblées directement à partir des fichiers extraits et poussées vers le registre via Crane.

Le processus concret :

Snapshots Debian (snapshot.debian.org)
  ↓ SHA256 checksums + timestamps
Bazel rules_distroless
  ↓ extraction sélective des .deb
rules_oci
  ↓ assemblage en layers OCI
gcr.io/distroless/*

Les mises à jour de sécurité sont gérées par des GitHub Actions qui génèrent automatiquement des PRs quand des paquets Debian sont mis à jour.

Les variantes disponibles

Toutes les images sont basées sur Debian 12 (Bookworm) ou Debian 13, avec des suffixes explicites.

Images de base :

ImageContenuTailleUsage
static-debian12Certificats TLS, tzdata, /etc/passwd. Pas de libc.~2 MBBinaires Go/Rust statiques
base-debian12static + glibc + libssl~20 MBBinaires dynamiques (C, Rust/glibc)
cc-debian12base + libgcc + libstdc++~25 MBApplications C/C++

Images avec runtime :

RuntimeImageTaille approx.
Node.js 22gcr.io/distroless/nodejs22-debian12~141 MB
Python 3gcr.io/distroless/python3-debian12~52 MB
Java 21gcr.io/distroless/java21-debian12~220 MB

Tags disponibles pour chaque image :

TagDescription
:latestVersion courante
:nonrootExécution sous UID 65534 (non-root)
:debugInclut un shell BusyBox pour le troubleshooting
:debug-nonrootDebug + non-root

Exemple pratique — Node.js sur distroless

FROM node:22 AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci

FROM deps AS build
COPY . .
RUN npm run build

FROM node:22 AS prod-deps
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev

FROM gcr.io/distroless/nodejs22-debian12:nonroot
WORKDIR /app
COPY --from=prod-deps /app/node_modules ./node_modules
COPY --from=build /app/dist ./dist
COPY package.json ./
EXPOSE 4321
CMD ["dist/server/entry.mjs"]

Point critique : le CMD (et ENTRYPOINT) doivent utiliser la forme JSON (["..."]), pas la forme shell ("..."). La forme shell nécessite /bin/sh, qui n’existe pas. Cette image distroless Node.js utilise le binaire Node.js comme entrypoint — le chemin passé en CMD est le fichier à exécuter, pas une commande shell.

Adopteurs notables

Le projet distroless est utilisé par Kubernetes (depuis v1.15), Knative, Tekton, Teleport, et BloodHound entre autres.

Limites

  • Difficile à étendre : si vous avez besoin d’un paquet qui n’est pas dans les images pré-construites, il faut maîtriser Bazel — la courbe d’apprentissage est raide.
  • Pas de SLA formel sur les CVEs : les mises à jour dépendent du rythme des PRs automatisées.
  • Base Debian uniquement : si votre stack de développement est sur Ubuntu, les versions de paquets peuvent diverger légèrement.

3. Chainguard — Wolfi, apko et le zéro CVE

L’entreprise et sa philosophie

Chainguard a été fondée par les ingénieurs Google qui ont créé Sigstore, le standard open source de signature cryptographique. L’entreprise a levé $892M au total (dont une Series D de $356M début 2025) et atteint $40M d’ARR en 2025.

Leur approche diffère fondamentalement de Google Distroless : au lieu de reconstruire à partir de paquets Debian, Chainguard a créé son propre OS et ses propres outils de build.

Wolfi — la “undistro” Linux

Wolfi est un OS Linux conçu exclusivement pour les containers. Ce n’est pas une distribution complète — il n’a pas de kernel (il repose sur celui de l’hôte via le runtime de container).

Le choix technique le plus important de Wolfi : utiliser glibc (pas musl comme Alpine). C’est ce qui le distingue fondamentalement d’Alpine et résout le problème de compatibilité musl/glibc qui affecte Alpine :

AspectAlpine (musl)Wolfi (glibc)
Wheels Python pré-compiléesSouvent recompilées depuis les sourcesFonctionnent directement
Modules natifs Node.jsPas officiellement supportésSupport complet
Résolution DNSComportements inhabituels connusStandard
Allocation mémoire sous chargeJusqu’à 2x plus lentPerformance standard
Format de paquetsapkapk (mais non interchangeable avec Alpine !)

Attention : malgré le format .apk commun, les paquets Alpine et Wolfi ne sont pas interchangeables. Ne jamais copier des binaires Alpine dans un container Wolfi (ni l’inverse).

apko — construction déclarative d’images

apko est l’outil qui transforme un fichier YAML en image OCI. Pas de Dockerfile, pas de commandes RUN — tout est déclaratif :

# apko.yaml — exemple d'image Node.js minimale
contents:
  repositories:
    - https://packages.wolfi.dev/os
  keyring:
    - https://packages.wolfi.dev/os/wolfi-signing.rsa.pub
  packages:
    - wolfi-baselayout
    - nodejs-22
    - ca-certificates-bundle

accounts:
  groups:
    - groupname: nonroot
      gid: 65532
  users:
    - username: nonroot
      uid: 65532
  run-as: 65532

entrypoint:
  command: /usr/bin/node

archs:
  - amd64
  - arm64

environment:
  NODE_ENV: production
apko build apko.yaml my-node:latest output.tar
# OU pousser directement vers un registre :
apko publish apko.yaml registry.example.com/my-node:latest

L’absence de RUN n’est pas un oubli — c’est un choix de design. Comme l’explique la documentation apko, cette contrainte garantit que les images sont reproductibles bit-à-bit : le même YAML produit toujours la même image binaire. Et chaque build génère automatiquement un SBOM (Software Bill of Materials).

melange — construction de paquets APK

Si vous avez besoin d’un paquet qui n’existe pas dans le dépôt Wolfi, melange permet d’en construire un via un pipeline YAML :

package:
  name: my-app
  version: 1.0.0

environment:
  contents:
    packages:
      - wolfi-baselayout
      - build-base
      - nodejs-22
      - npm

pipeline:
  - runs: |
      npm ci --omit=dev
      mkdir -p ${{targets.destdir}}/app
      cp -r node_modules dist package.json ${{targets.destdir}}/app/

melange produit des .apk que apko peut directement consommer — c’est le combo melange + apko qui remplace le traditionnel Dockerfile multi-stage pour les utilisateurs Chainguard.

Le catalogue d’images

Chainguard propose plus de 2 000 images couvrant la plupart des stacks : Node.js, Python, Go, Java, nginx, PostgreSQL, Redis, etc.

Tarification :

TierImagesTagsSLA CVECoût
Free~50 images curatées:latest et :latest-dev uniquementAucunGratuit
Production2 000+ imagesVersions spécifiques + timestamps7j critique, 14j haut/moyen/basPayant (par catalogue ou par image)

Différence clé avec Google Distroless : Chainguard reconstruit chaque image chaque nuit automatiquement via leur plateforme DriftlessAF, ce qui intègre les patchs de sécurité en quelques heures. Google Distroless fonctionne par PRs plus espacées.

Exemple pratique — Node.js sur Chainguard

# Build stage — inclut npm, shell, outils de build
FROM cgr.dev/chainguard/node:latest-dev AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# Production — distroless, pas de shell
FROM cgr.dev/chainguard/node:latest
WORKDIR /app
COPY --from=builder --chown=node:node /app/dist ./dist
COPY --from=builder --chown=node:node /app/node_modules ./node_modules
COPY --from=builder --chown=node:node /app/package.json ./
ENV NODE_ENV=production
CMD ["dist/index.js"]

Le tag :latest-dev inclut un shell et npm pour le build stage. Le tag :latest est l’image distroless de production — uniquement le binaire Node.js et ses dépendances système.


4. Ubuntu Chiseled — le ciseau de Canonical

Le concept

Ubuntu Chiseled est la réponse de Canonical au mouvement distroless. L’idée : plutôt que de reconstruire un OS from scratch (Wolfi) ou d’extraire des paquets Debian via Bazel (Google), Canonical utilise un outil appelé chisel pour découper chirurgicalement les paquets Ubuntu en ne gardant que les fichiers strictement nécessaires.

La métaphore officielle est celle de Michel-Ange : la sculpture existe déjà dans le bloc de marbre — chisel retire le superflu.

Comment chisel fonctionne

chisel est un outil CLI en Go qui décompose les paquets .deb d’Ubuntu en composants plus fins appelés slices (tranches). Contrairement à apt qui installe des paquets entiers, chisel n’installe que les fichiers listés dans la définition d’une slice.

Installation :

go install github.com/canonical/chisel/cmd/chisel@latest

Usage basique :

chisel cut --release ubuntu-24.04 --root /rootfs/ \
  base-files_base \
  ca-certificates_data \
  libc6_libs \
  libssl3t64_libs

Cette commande :

  1. Télécharge les paquets .deb depuis les archives Ubuntu
  2. Extrait uniquement les fichiers définis dans chaque slice
  3. Résout les dépendances inter-slices
  4. Place le tout dans /rootfs/

Les slices en détail

Le concept central de chisel est la slice — un sous-ensemble minimal et fonctionnel d’un paquet Debian. Les définitions sont stockées dans le dépôt canonical/chisel-releases, organisé par branches Git (une par version d’Ubuntu : ubuntu-22.04, ubuntu-24.04, etc.).

Prenons un exemple concret. Le paquet libc6 contient des dizaines de fichiers : bibliothèques partagées, configurations, documentation, fichiers de localisation. La définition de slices pour libc6 le découpe en :

  • libc6_libs — les bibliothèques partagées (.so) uniquement
  • libc6_config — les fichiers de configuration
  • libc6_copyright — les informations de licence

Quand une application a besoin de glibc, on n’installe que libc6_libs — pas la configuration, pas la documentation, pas les locales.

Format d’une définition de slice :

# slices/vim-tiny.yaml dans chisel-releases
package: vim-tiny

essential:
  - vim-tiny_copyright

slices:
  bins:
    essential:
      - libacl1_libs
      - libc6_libs
      - libtinfo6_libs
      - vim-common_config
    contents:
      /usr/bin/vim.tiny:

  config:
    contents:
      /etc/vim/vimrc.tiny:

  copyright:
    contents:
      /usr/share/doc/vim-tiny/copyright:

Chaque slice déclare ses dépendances (champ essential) sur d’autres slices — pas sur des paquets entiers, mais sur des slices spécifiques d’autres paquets. C’est ce qui permet la granularité fine.

La convention de nommage est <paquet>_<slice> avec un underscore, ce qui fonctionne car les noms de paquets Debian ne peuvent pas contenir d’underscores.

Construire une image chiseled custom

La méthode recommandée utilise un Dockerfile multi-stage :

ARG UBUNTU_RELEASE=24.04

# Stage 1 : construire le rootfs chiseled
FROM ubuntu:${UBUNTU_RELEASE} AS builder
RUN apt-get update && apt-get install -y ca-certificates

# Installer chisel
ADD https://github.com/canonical/chisel/releases/download/v1.1.0/chisel_v1.1.0_linux_amd64.tar.gz /tmp/
RUN tar -xzf /tmp/chisel_*.tar.gz -C /usr/bin/

# Découper les slices nécessaires
RUN mkdir -p /rootfs
RUN chisel cut --release "ubuntu-${UBUNTU_RELEASE}" --root /rootfs \
  base-files_base \
  base-files_release-info \
  ca-certificates_data \
  libc6_libs \
  libssl3t64_libs \
  libstdc++6_libs

# Préparer un utilisateur non-root
RUN echo "appuser:x:1000:1000::/home/appuser:/sbin/nologin" >> /rootfs/etc/passwd && \
    echo "appuser:x:1000:" >> /rootfs/etc/group && \
    mkdir -p /rootfs/home/appuser

# Stage 2 : image finale from scratch
FROM scratch
COPY --from=builder /rootfs /
USER appuser
WORKDIR /home/appuser

On part de scratch, mais avec un rootfs pré-rempli par chisel contenant uniquement les slices nécessaires. Le résultat : une image Ubuntu-compatible (glibc, certificats TLS) mais sans shell, sans apt, sans rien de superflu.

Le partenariat Microsoft / .NET

Le cas d’usage le plus mature de chisel est le partenariat entre Microsoft et Canonical pour les images .NET. Les résultats sont parlants :

MétriqueUbuntu standardUbuntu Chiseled
Taille (aspnet 8.0)216 MB110 MB
Composants Linux92 paquets7 paquets
Temps de démarrageBaseline~20% plus rapide
Utilisation mémoireBaseline~20% plus faible
Temps de réponseBaseline~40% plus rapide

Richard Lander (Microsoft) a qualifié les images chiseled de “base image recommandée pour les développeurs .NET”.

Images pré-construites disponibles

RuntimeImage Docker Hub
.NET Runtimemcr.microsoft.com/dotnet/runtime:8.0-jammy-chiseled
ASP.NET Coremcr.microsoft.com/dotnet/aspnet:8.0-jammy-chiseled
Java JREubuntu/jre (JRE 8, 17, 21)
Node.jscanonical/chiseled-nodejs

Limites

  • Couverture de slices limitée : vous ne pouvez installer que les paquets pour lesquels une définition de slice existe dans chisel-releases. Pour un paquet non couvert, il faut écrire sa propre définition YAML et la contribuer au dépôt.
  • Pas de version pinning : chisel utilise la version du paquet présente dans les archives Ubuntu au moment du build. Pas de possibilité de figer une version spécifique de libc6.
  • Compatibilité des scanners : certains scanners de vulnérabilités (Trivy, Grype) ne reconnaissent pas les images chiseled car le fichier /var/lib/dpkg/status est absent. Il faut générer un fichier de statut dpkg via chisel-wrapper pour que les scanners fonctionnent.
  • Dernière release : v1.4.0 (13 février 2026).

5. Docker Hardened Images — le nouveau venu

Le contexte

En mai 2025, Docker a lancé les Hardened Images (DHI) comme produit commercial. En décembre 2025, revirement stratégique : Docker a rendu les 1 000+ images DHI gratuites et open source sous licence Apache 2.0, sans restrictions d’usage ni de redistribution.

C’est une réponse directe à la montée de Chainguard — et un signal fort de validation du marché des images zéro CVE.

Ce qu’elles offrent

Les Docker Hardened Images sont des images distroless construites à partir de Debian et Alpine, avec :

  • Jusqu’à 95% de vulnérabilités en moins par rapport aux images communautaires
  • Exécution non-root par défaut
  • SBOMs inclus avec provenance SLSA Build Level 3
  • Signatures cryptographiques
  • Un tier Enterprise (payant) avec engagement de remédiation CVE critique sous 7 jours
  • Un Extended Lifecycle Support (payant) ajoutant 5 ans de couverture sécurité au-delà de la fin de vie upstream

Accès

Les images sont disponibles sur Docker Hub avec le préfixe docker/ :

docker pull docker/nginx-dhx:latest

La documentation officielle DHI détaille les concepts et l’utilisation.


6. Outils spécialisés

Au-delà des approches “image de base”, plusieurs outils permettent de construire ou optimiser des images minimales.

ko — images Go sans Docker

ko est un outil CNCF Sandbox qui construit des images pour les applications Go sans démon Docker. Il exécute go build localement, package le binaire dans une image OCI, et la pousse vers un registre.

export KO_DOCKER_REPO=registry.example.com/my-project
ko build ./cmd/myapp

Par défaut, ko utilise cgr.dev/chainguard/static comme image de base (changement récent — c’était auparavant gcr.io/distroless/static:nonroot). Il génère aussi un SBOM automatiquement.

Configuration .ko.yaml :

defaultBaseImage: cgr.dev/chainguard/static:latest

baseImageOverrides:
  github.com/my-org/my-app/cmd/web: cgr.dev/chainguard/glibc-dynamic:latest

ko est utilisé par Knative, Tekton, Karpenter, Kyverno, et Sigstore.

SlimToolkit — optimiser des images existantes

SlimToolkit (anciennement DockerSlim, désormais projet CNCF Sandbox) prend une approche radicalement différente : au lieu de construire une image minimale from scratch, il analyse une image existante au runtime et supprime tout ce qui n’est pas utilisé.

Résultats annoncés :

  • Node.js : 432 MB → 14 MB (30x)
  • Python : 916 MB → 27.5 MB (33x)
  • Go : 700 MB → 1.56 MB (449x)
slim build --target my-fat-image:latest

Le tradeoff : SlimToolkit nécessite une phase de profiling qui exécute l’application — si tous les chemins de code ne sont pas exercés pendant l’analyse, les fichiers nécessaires mais non utilisés pendant le profiling seront supprimés. C’est un outil puissant mais qui demande de la rigueur dans la phase d’analyse.


7. Sécurité : signatures, SBOM et supply chain

Les images distroless ne se limitent pas à réduire la taille — elles s’inscrivent dans un mouvement plus large de sécurisation de la supply chain logicielle.

Cosign et Sigstore

Cosign (partie du projet Sigstore) permet de signer et vérifier cryptographiquement des images de containers. Toutes les images Google Distroless et Chainguard sont signées.

Vérifier une image Google Distroless :

cosign verify gcr.io/distroless/nodejs22-debian12 \
  --certificate-oidc-issuer https://accounts.google.com \
  --certificate-identity keyless@distroless.iam.gserviceaccount.com

Le modèle de signature “keyless” fonctionne sans clé privée persistante :

  1. Authentification via OIDC (Google, GitHub, Microsoft)
  2. Demande d’une paire de clés éphémère
  3. Signature de l’artefact
  4. Enregistrement dans un log de transparence (Rekor)
  5. Destruction de la clé privée

Plus de clés à gérer, à sécuriser, à faire tourner.

SBOM (Software Bill of Materials)

Un SBOM est l’inventaire complet des composants d’une image. La distinction importante : les SBOMs peuvent être générés au moment du build (ce que font apko et Chainguard) ou après coup par scanning (ce que font Trivy, Syft). Les SBOMs au build sont plus précis car ils savent exactement ce qui a été installé, tandis que les scanners peuvent manquer des composants ou en identifier de faux.

Récupérer le SBOM d’une image Chainguard :

cosign download attestation cgr.dev/chainguard/node:latest \
  | jq -r '.payload' | base64 -d | jq .

Le contre-argument

Red Hat a publié un article argumentant que les images distroless ne sont pas la solution de sécurité qu’on imagine. Leur point : l’application elle-même reste la surface d’attaque principale. Supprimer le shell est un obstacle mineur pour un attaquant déterminé — il peut toujours exploiter l’application directement, exfiltrer des données via le réseau, ou utiliser des techniques de living-off-the-land.

C’est un argument valide — le distroless n’est pas une silver bullet. C’est une couche de défense en profondeur qui rend l’exploitation plus difficile, pas impossible.


8. Déboguer un container sans shell

La question inévitable : “comment je debug si je ne peux même pas faire un docker exec ?“

1. Images debug / -dev

Toutes les approches distroless proposent des variantes avec shell :

# Google Distroless — tag :debug (BusyBox)
docker run -it gcr.io/distroless/nodejs22-debian12:debug sh

# Chainguard — tag :latest-dev (shell + apk)
docker run -it cgr.dev/chainguard/node:latest-dev sh

En production, utilisez l’image distroless. En développement/debug, basculez temporairement sur la variante debug.

2. Containers éphémères Kubernetes

Depuis Kubernetes 1.25 (stable), kubectl debug permet d’attacher un container temporaire à un pod existant :

kubectl debug -it pod/myapp --image=busybox --target=myapp

Le container éphémère partage le namespace de processus du container cible — vous pouvez voir ses processus, inspecter ses fichiers, diagnostiquer des problèmes réseau.

3. cdebug — le couteau suisse

cdebug (par Ivan Velichko, créateur d’iximiuz Labs) est un outil open source qui lance un container sidecar attaché aux namespaces process et réseau du container cible :

# Docker
cdebug exec -it mycontainer

# Kubernetes
cdebug exec -it --privileged pod/myapp/myapp

4. docker debug (Docker Desktop 4.27+)

Une fonctionnalité de Docker Desktop qui injecte un environnement Nix dans le container. Nécessite une licence Docker Desktop Pro.

5. Partage de namespaces manuels

docker run --rm -it \
  --pid container:target \
  --network container:target \
  busybox sh

Vous obtenez un shell dans un container BusyBox qui partage les namespaces PID et réseau du container cible.


9. Comparatif synthétique

Par critère technique

scratchGoogle DistrolessChainguardUbuntu ChiseledDocker DHI
BaseRienDebianWolfiUbuntuDebian/Alpine
libcAucuneglibcglibcglibcglibc/musl
Taille (base)0 B~2-20 MB~2-20 MB~5-11 MBVariable
ShellNonNon (sauf :debug)Non (sauf -dev)NonNon
Build toolDockerfileBazelapko + melangechisel + DockerfilePropriétaire
SBOMNonNon par défautOui (build-time)Non par défautOui
SignaturesNonCosign (keyless)Cosign (keyless)NonOui
CVE SLAN/AAucun7j critique (payant)Via Ubuntu Pro7j critique (payant)
Rebuild autoN/APR-basedNightlyLTS scheduleNon documenté
ExtensibilitéTotale mais manuelleDifficile (Bazel)Facile (YAML)Moyenne (YAML slices)Limitée
CoûtGratuitGratuitFree tier + payantGratuit (+ Pro)Gratuit (+ Enterprise)

Par langage/runtime

LangageRecommandation distrolessAlternative
Go (static)scratch ou gcr.io/distroless/staticko (auto)
Go (CGO)gcr.io/distroless/baseChainguard glibc-dynamic
Rust (musl)scratch
Rust (glibc)gcr.io/distroless/ccChainguard cc
Node.jsgcr.io/distroless/nodejs22Chainguard node
Pythongcr.io/distroless/python3Chainguard python
Javagcr.io/distroless/java21Ubuntu Chiseled JRE
.NETUbuntu Chiseled (jammy-chiseled)
Statique (HTML/CSS)scratch + serveur HTTP statiquenginx Chainguard

10. Focus Node.js — pourquoi pas Ubuntu Chiseled ?

Le tableau synthétique ci-dessus mentionne Ubuntu Chiseled pour Java et .NET, mais pas pour Node.js. Ce n’est pas un oubli — c’est le reflet de la réalité du terrain.

L’état des lieux

Canonical propose une image chiseled Node.js via le dépôt canonical/chiseled-nodejs. En février 2026, voici ce qu’on constate :

IndicateurValeur
Stars GitHub3
Commits totaux~19
Dernière activitéSeptembre 2025 (refactoring)
Versions Node.js18 seulement (Ubuntu 24.04)
Node.js 20PR ouverte, non mergée
Node.js 22Slice dispo dans chisel-releases (Ubuntu 24.10+), mais pas de rock officiel
Releases formellesAucune
Retours de productionAucun publié

Pour comparaison, Node.js 18 a atteint sa fin de vie en avril 2025. L’image officielle Canonical ne propose donc qu’une version de Node.js qui n’est plus supportée upstream.

Pourquoi cette immaturité ?

La raison est structurelle. Les versions de Node.js disponibles dans chisel sont liées aux paquets Ubuntu :

Branche chisel-releasesPaquetVersion Node.js
ubuntu-22.04 (Jammy LTS)Pas de slice Node.js
ubuntu-24.04 (Noble LTS)libnode109Node.js 18.x
ubuntu-24.10 (Oracular)libnode115Node.js 22.x
ubuntu-25.04 (Plucky)libnode115Node.js 22.x

Source : canonical/chisel-releases, branches par version Ubuntu.

Le problème : Ubuntu 24.04 LTS (la base stable) embarque Node.js 18. Pour obtenir Node.js 22 via chisel, il faut utiliser Ubuntu 24.10 ou 25.04 — des releases non-LTS, donc sans le support long terme qui fait l’intérêt de Chiseled. On perd l’argument principal.

À titre de comparaison, le partenariat .NET avec Microsoft a bénéficié d’un investissement conjoint significatif : images officielles sur MCR, documentation complète, benchmarks publiés, intégration dans la chaîne d’outils .NET. Rien de comparable n’existe pour Node.js.

Comparatif détaillé des options Node.js distroless

Google DistrolessChainguardUbuntu Chiselednode:22-alpine (réf.)
Imagegcr.io/distroless/nodejs22-debian12cgr.dev/chainguard/nodeubuntu/nodenode:22-alpine
Disponible depuis~2018~2023Sept. 2023~2016
Étoiles GitHub22 284 (projet global)N/A (propriétaire)3N/A (officiel)
Versions Node.js20, 22, 24latest (gratuit) ; toutes (payant)18 seulementToutes LTS
Taille compressée~55-60 MB~55-60 MB~37 MB~55 MB
Taille décompressée~141 MB~145 MB~102 MB~155 MB
libcglibcglibcglibcmusl
npm inclusNonOuiNonOui
ShellNon (:debug = BusyBox)Non (:latest-dev = shell)NonOui
SBOMNonOui (build-time)NonNon
Signatures CosignOuiOuiNonNon
Fréquence rebuildQuotidienne (automatisée)Quotidienne (automatisée)RareRégulière
SLA CVEAucun7j critique (payant)Via Ubuntu ProAucun
Modules natifsDifficile (pas de compilateur)Meilleur (glibc + -dev)DifficilePossible (musl)
Compatibilité scannersOui (Debian dpkg)OuiProblématiqueOui

Trois scénarios concrets

1. Vous voulez du battle-tested, gratuit → Google Distroless

C’est le choix par défaut. Supporté depuis ~2018, utilisé par Kubernetes et Knative, mis à jour quotidiennement. Node.js 20, 22 et 24 sont disponibles. La seule contrainte : pas de npm dans l’image de production (multi-stage obligatoire).

FROM node:22 AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci && npm run build
RUN npm ci --omit=dev

FROM gcr.io/distroless/nodejs22-debian12:nonroot
WORKDIR /app
COPY --from=build /app/node_modules ./node_modules
COPY --from=build /app/dist ./dist
CMD ["dist/server/entry.mjs"]

2. Vous voulez zéro CVE avec npm dans l’image → Chainguard

Chainguard est la seule option distroless qui inclut npm dans l’image de production. Utile si vous avez des scripts postinstall ou des dépendances optionnelles résolues au démarrage. Le tag gratuit :latest est suffisant pour du développement ; la production sérieuse nécessite le tier payant pour le version pinning.

FROM cgr.dev/chainguard/node:latest-dev AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM cgr.dev/chainguard/node:latest
WORKDIR /app
COPY --from=build --chown=node:node /app/dist ./dist
COPY --from=build --chown=node:node /app/node_modules ./node_modules
COPY --from=build --chown=node:node /app/package.json ./
ENV NODE_ENV=production
CMD ["dist/server/entry.mjs"]

3. Vous voulez absolument du Chiseled Ubuntu Node.js → DIY

Si la cohérence Ubuntu dev/prod est une priorité absolue, il est possible de construire son propre image chiseled Node.js. Le projet communautaire nvitaterna/nodejs-distroless fait exactement ça : il utilise chisel pour produire des images Node.js 22 et 24 distroless basées sur Ubuntu. C’est plus activement maintenu que l’image officielle de Canonical.

Mais il faut être lucide sur ce que ça implique :

  • Vous dépendez des versions Node.js empaquetées par Ubuntu
  • Vous devez gérer la compatibilité des scanners (génération du fichier dpkg status)
  • Vous n’avez ni SBOM, ni signatures Cosign
  • Le support communautaire est quasi inexistant

Verdict Node.js

Pour Node.js en 2026, Ubuntu Chiseled n’est pas prêt pour la production. Le partenariat Canonical/.NET montre ce que Chiseled peut être quand il y a un investissement sérieux — mais cet investissement n’a pas eu lieu pour Node.js.

Votre prioritéChoix recommandé
Stabilité et track recordGoogle Distroless (nodejs22-debian12)
Zéro CVE + npm + DXChainguard (node:latest gratuit, ou payant pour pinning)
Cohérence Ubuntu dev/prodAttendre la maturation de Chiseled, ou utiliser node:22-slim en attendant
Taille minimale absolueChiseled DIY (~37 MB compressé) — mais au prix de la maturité

11. Quelle image choisir ?

Le choix dépend de trois facteurs : votre langage, votre tolérance au risque, et votre budget.

Arbre de décision

Votre binaire est-il 100% statiquement lié ?
├── Oui → scratch (ou distroless/static pour les certs TLS)
└── Non → Quel runtime ?
    ├── Go (avec CGO) → distroless/base ou Chainguard glibc-dynamic
    ├── Node.js → distroless/nodejs22 ou Chainguard node
    ├── Python → distroless/python3 ou Chainguard python
    ├── Java → distroless/java21 ou Ubuntu Chiseled JRE
    ├── .NET → Ubuntu Chiseled (partenariat Microsoft)
    └── Autre → évaluer Chainguard ou construire custom avec chisel

Avez-vous besoin d'un SLA sur les CVEs ?
├── Oui → Chainguard Production ou Docker DHI Enterprise
└── Non → Google Distroless ou les free tiers

Votre stack est-elle Ubuntu-native ?
├── Oui → Ubuntu Chiseled (même paquets dev → prod)
└── Non → Google Distroless ou Chainguard

Ce qui ne change pas

Quelle que soit l’approche choisie, les fondamentaux restent les mêmes :

  1. Multi-stage build obligatoire — les outils de build (npm, pip, go, cargo) restent dans le build stage.
  2. CMD/ENTRYPOINT en forme JSON["node", "app.js"] pas "node app.js".
  3. Utilisateur non-root:nonroot tags ou USER directive.
  4. Pas de secrets dans l’image — utiliser les secret mounts BuildKit ou des variables d’environnement au runtime.
  5. Scanner régulièrement — même une image zéro CVE aujourd’hui peut en avoir demain. Intégrer Trivy, Grype ou Snyk dans la CI.