Vous n’avez pas reçu d’alerte parce que votre image conteneur était 40 Mo plus grosse. Vous avez reçu l’alerte parce que le TLS a planté à 02:13, que les résolutions DNS
ont commencé à expirer, que des wheels Python ont refusé de s’installer, ou que votre image « minimale » n’avait absolument aucun outil pour vérifier ce qui se passait.
Alpine et Debian-slim vont bien toutes les deux. Mais Internet continue de présenter cette décision comme « petite vs plus petite », et c’est ainsi que des équipes
tout à fait compétentes livrent des conteneurs rapides en CI mais pénibles en production.
La règle générale (et quand la contourner)
Si votre service dépend de bibliothèques natives, de binaires précompilés, d’agents fournis par des éditeurs, ou de tout ce que vous n’avez pas compilé vous-même : commencez avec Debian-slim.
Vous obtenez glibc, un packaging prévisible, et moins de cas limites étranges. Vous obtenez aussi une meilleure ergonomie pour les moments « il est 3 h du matin et je dois voir ce qu’il y a dedans ».
Si vous livrez un unique binaire statique (Go avec CGO_ENABLED=0, Rust ciblant musl, ou une build maîtrisée) et que vous savez exactement ce dont vous avez besoin :
Alpine peut être excellent. C’est aussi pertinent pour de petites images utilitaires où vous voulez la simplicité de BusyBox et où vous contrôlez chaque dépendance.
Si vous choisissez une image de base principalement parce que le chiffre de la taille compressée fait joli dans une diapositive, arrêtez. Votre image de base est un choix opérationnel :
libc, comportement du résolveur, pile TLS, gestionnaire de paquets, surface de débogage et cadence de correctifs. La taille compte, mais ce n’est généralement pas ce qui vous réveillera la nuit.
Blague #1 : Choisir Alpine parce que c’est plus petit, c’est comme acheter une moto parce qu’elle rentre dans votre cuisine. Techniquement vrai, émotionnellement suspect.
Un arbre de décision simple
- Besoin de compatibilité glibc (la plupart des logiciels Linux précompilés l’attendent) : choisissez Debian-slim.
- Besoin de compiler des wheels Python, des modules natifs Node, ou d’utiliser des binaires fournisseurs : choisissez Debian-slim.
- Vous livrez un unique binaire statique, pas de scripts shell, pas de compilation au runtime : Alpine est viable.
- Besoin d’un débogage d’incident rapide à l’intérieur du conteneur : Debian-slim (ou ajoutez une image de debug dédiée).
- Vous voulez l’image finale la plus petite possible : utilisez des builds multi-stage ; envisagez distroless. N’utilisez pas Alpine comme pansement.
Faits & contexte que vous pouvez utiliser dans un argument
Ce ne sont pas des anecdotes. Ce sont les éléments de fond « pourquoi ça se produit » qui transforment les débats sur l’image de base de sensations en ingénierie.
- Alpine utilise musl libc, pas glibc. Beaucoup de binaires et de comportements libc subtils diffèrent (locales, DNS, cas limites de threading).
- Le userland d’Alpine repose largement sur BusyBox. Les outils courants existent, mais leurs options et comportements peuvent différer des GNU coreutils.
- Debian-slim reste Debian : glibc, apt/dpkg, et l’écosystème que la plupart des logiciels Linux ciblent par défaut.
- « Slim » consiste à supprimer docs/locales et quelques paquets, pas à changer les attentes ABI fondamentales des binaires Linux.
- Le comportement du résolveur DNS de musl est différent de celui de glibc, et cette différence est apparue en production sous charge ou avec un resolv.conf mal configuré.
- De nombreux écosystèmes de langages distribuent des wheels/binaires ciblant glibc (wheels Python, modules Node précompilés, agents fournisseurs). musl force souvent la compilation depuis les sources.
- La liaison statique n’est pas sans coût. Elle peut réduire les dépendances à l’exécution mais compliquer le patching (on corrige en recompilant l’app, pas en patchant la base).
- Les scanners de sécurité de conteneurs comptent les paquets. Les images Debian peuvent afficher « plus de CVE » simplement parce qu’elles contiennent plus de paquets à comparer aux bases de données.
- Alpine a historiquement mis en avant le minimalisme et la sécurité, mais la sécurité dépend des mises à jour, des contrôles de la chaîne d’approvisionnement et de la posture à l’exécution — pas seulement de fichiers en moins.
Les vrais compromis : libc, paquets, débogage, sécurité, performance
1) Compatibilité libc : musl vs glibc, c’est le cœur du sujet
Le musl d’Alpine est axé sur la conformité et la légèreté. glibc est… glibc : une énorme surface de compatibilité, des décennies de « cela doit rester tel quel parce que quelqu’un l’a livré en 2009 ». Dans le monde des conteneurs, la compatibilité gagne généralement.
Mode de défaillance : vous docker run quelque chose qui fonctionnait sur Ubuntu, et ça plante sur Alpine avec une erreur du linker. Ou ça tourne, mais avec un comportement étrange sous concurrence, DNS ou gestion des locales. Votre ticket d’incident lira « timeouts aléatoires » ou « échoue uniquement en prod », ce qui est toujours amusant.
Si vous déployez des binaires tiers (clients de base de données, agents d’observabilité, renderers PDF, outils médias), supposez glibc jusqu’à preuve du contraire. Alpine peut fonctionner, mais vous paierez la « taxe d’investigation ».
2) Écosystème de paquets : apk vs apt n’est pas qu’une question de syntaxe
apk d’Alpine est rapide et simple. apt de Debian est plus lourd mais massivement compatible. La différence majeure est
plutôt quels paquets existent, à quelle fréquence vous devez compiler depuis les sources, et quels choix sont inclus par défaut (bundles de certificats, locales, fuseau horaire).
Si votre Dockerfile inclut des « dépendances de build » (compilateurs, headers) et que vous essayez de garder l’image runtime petite, vous devriez de toute façon utiliser un build multi-stage. Le gestionnaire de paquets devient alors un outil de build, pas un mode de vie.
3) Débogage : les images minimales sont super jusqu’à ce que vous deviez déboguer
En production, vous avez besoin d’un plan pour : « Comment voir le DNS, les routes, les chaînes TLS et l’état des processus ? » Debian-slim est souvent plus accueillant ici.
Alpine vous force souvent à installer des outils en plein incident, ce qui est une excellente façon de découvrir que vous avez bloqué l’egress sortant pour de bonnes raisons.
Il existe des façons mûres de déboguer (conteneurs de debug éphémères, sidecars, tags de debug). Mais si vous ne les avez pas, ne choisissez pas une image de base qui vous rend aveugle.
4) Sécurité : moins de paquets peut signifier moins d’alertes, pas moins de risque
L’empreinte réduite d’Alpine donne souvent moins de résultats dans les scanners. Ce n’est pas la même chose qu’être plus sûr. Les scanners de vulnérabilités font du matching sur des bases de paquets avec des qualités variables, pas des verdicts absolus.
La question opérationnelle de sécurité est : pouvez-vous patcher rapidement, rebuild de manière déterministe, et déployer ? La cadence stable de Debian et son large écosystème aident.
La cadence d’Alpine est aussi correcte, mais vous devez la suivre. Dans les deux cas, vous avez besoin d’automatisation pour rebuild les images, pas d’espoir.
5) Performance : parfois Alpine est plus lent, parfois plus rapide, le plus souvent c’est compliqué
Les gens aiment prétendre qu’Alpine est « plus rapide » parce qu’il est plus petit. Le temps de démarrage n’est que rarement dominé par quelques mégaoctets de couches ;
il est généralement lié à l’initialisation du runtime, au warmup JIT, à la latence DNS, aux caches froids et aux dépendances avales.
Le piège performance vient des builds depuis les sources. Sur Alpine, vous pouvez compiler des dépendances qui s’installeraient autrement comme wheels/binaries précompilés sur Debian.
Cela peut ralentir la CI, rendre les builds moins reproductibles, et parfois produire des chemins d’exécution différents de ceux testés ailleurs.
6) Le cadrage fiabilité
Je me soucie de deux choses : pouvons-nous livrer des changements rapidement, et pouvons-nous déboguer quand ça casse ? C’est tout. Une image de base plus petite est agréable, mais ce n’est pas une stratégie de fiabilité.
Citation (idée paraphrasée) : Gene Kim insiste souvent sur le fait qu’améliorer la fiabilité vient de raccourcir les boucles de rétroaction et rendre le travail visible — pas de compter sur du débogage héroïque.
Conseils par runtime (Go, Java, Node, Python, Rust, Nginx)
Go
Si vous pouvez produire un vrai binaire statique (CGO_ENABLED=0) et que vous n’avez pas besoin de fonctionnalités dépendantes de libc, Alpine convient.
Vous pouvez même aller plus petit qu’Alpine en utilisant scratch ou distroless pour l’image runtime, avec une image de debug disponible au besoin.
Si vous utilisez cgo (drivers de base de données, traitement d’images, intégrations système), la compatibilité glibc devient pertinente. Debian-slim est généralement le choix sûr par défaut.
Java / JVM
Choisissez une image de base qui correspond aux attentes de votre distribution JDK/JRE. Beaucoup de distributions JVM supposent glibc et des outils standards.
Des images JVM basées sur Alpine existent, mais il faut être volontaire sur les certificats CA, les polices (oui, les polices) et le comportement DNS.
Node.js
Les projets Node avec des modules natifs (node-gyp, bcrypt, sharp, grpc, canvas) sont l’endroit où la douleur Alpine prospère. Vous allez soit compiler
les modules depuis les sources (bonjour la toolchain de build), soit batailler avec des artefacts précompilés ciblant glibc. Debian-slim évite la plupart de ces problèmes.
Python
Python sur Alpine signifie souvent « compilation depuis les sources » pour tout ce qui a des dépendances natives (cryptography, lxml, numpy, pandas). Ce n’est pas toujours mauvais,
mais c’est coûteux opérationnellement. Debian-slim obtient généralement des wheels manylinux précompilés qui « fonctionnent simplement ».
Rust
Rust peut viser musl et produire des binaires quasi-statiques. C’est un véritable avantage pour des images runtime minimales. Assurez-vous simplement de savoir comment vous patcherez
les dépendances : vous patcherez en recompilant le binaire, pas en faisant un apt upgrade d’une libc.
Nginx / Envoy / HAProxy
Pour le proxy web standard, les deux conviennent. Le choix se résume généralement à l’écosystème de modules et à vos habitudes de débogage.
Si vous voulez des outils familiers et des modules prévisibles, Debian-slim est confortable. Si vous voulez une plus petite surface et que vous avez confiance dans votre pipeline de build, Alpine peut fonctionner.
Trois mini-histoires du monde de l’entreprise
Mini-histoire #1 : L’incident causé par une mauvaise hypothèse
Une équipe a migré un ensemble d’API internes de « quelle que soit l’image de base qu’on avait » vers Alpine pour réduire la taille d’image et accélérer les déploiements. Rien d’exotique :
service Node.js, parle à Postgres, appelle quelques APIs partenaires en HTTPS, envoie des logs. CI était verte, la préproduction avait l’air correcte.
Deux semaines plus tard, la production a commencé à montrer des timeouts sporadiques sur les appels HTTPS sortants. Pas tous les appels. Pas toutes les régions. Et bien sûr pas en préprod.
Les tableaux de bord montraient des pics de latence, puis une cascade de retries, puis le partenaire qui les limitait. Spirale de fiabilité classique : les retries transforment un petit problème en gros bruit.
La fausse hypothèse était « DNS = DNS ». Le comportement du résolveur du conteneur différait selon la configuration spécifique du resolv.conf et la façon dont le cluster injectait les domaines de recherche.
Sous charge, ces tentatives de recherche supplémentaires et le cache différent ont amplifié la latence. Le service n’était pas « cassé », il était systématiquement plus lent pour la résolution de noms d’une manière qui ne comptait qu’avec le trafic de production.
La correction n’a pas été héroïque : ils ont remis ce service sur Debian-slim pendant qu’ils testaient la configuration du résolveur et réduisaient les domaines de recherche.
La latence est redevenue normale immédiatement. Plus tard, ils ont re-testé Alpine avec un banc d’essai dédié et une configuration explicite du résolveur. Mais la leçon est restée :
changer l’image de base change le comportement runtime, ce n’est pas cosmétique.
Mini-histoire #2 : L’optimisation qui a mal tourné
Une autre organisation voulait des builds plus rapides et moins d’alertes de scanners. Quelqu’un a proposé une règle unique : « Tout sur Alpine. L’uniformité réduit la charge. »
Ils ont mis à jour une douzaine de services, y compris des jobs de traitement Python horaires jugés « suffisamment simples ».
La première douleur est apparue en CI : les installations sont devenues plus longues car des paquets qui étaient autrefois des wheels sont devenus des builds source. Pour que ça passe, les ingénieurs ont ajouté
des dépendances de build : compilateurs, headers, et assez de toolchain pour faire rougir une petite distribution Linux. Les Dockerfiles sont devenus complexes. Les miss de cache sont devenus plus coûteuses.
Puis c’est arrivé en production : un changement dans une dépendance native a causé une différence subtile de performance. Rien n’a explosé. C’était juste plus lent. Le job a commencé à chevaucher l’exécution suivante,
ce qui a augmenté la charge sur la base de données partagée, ce qui a ralenti d’autres services. Personne ne pouvait pointer une requête en échec unique. Tout était « un peu pire ».
Ils ont finalement scindé la flotte : les services purs avec binaires statiques ont obtenu des images minimales ; les services Python/Node sont revenus à Debian-slim avec des builds multi-stage et un verrouillage agressif des dépendances.
L’uniformité a été remplacée par une règle plus simple : « Choisir la compatibilité par défaut ; optimiser seulement quand c’est important. » Ennuyant. Efficace.
Mini-histoire #3 : La pratique ennuyeuse mais correcte qui a sauvé la situation
Une équipe plateforme a imposé une politique qui semblait fastidieuse : chaque service doit avoir (1) une étape de build, (2) une étape runtime, et (3) un tag debug optionnel contenant des outils de dépannage et correspondant à la libc runtime.
Un vendredi, un service lié aux paiements a commencé à échouer les handshakes TLS vers un endpoint tiers. Le fournisseur avait fait pivoter un CA intermédiaire.
Le service fonctionnait dans un cluster et était cassé dans un autre, ce qui est la manière typique d’avoir un weekend.
Parce qu’ils avaient une variante d’image debug, l’astreinte a pu la monter et inspecter immédiatement la chaîne de certificats, les versions du bundle CA et le comportement d’OpenSSL
sans rebuild l’image en plein incident. Ils ont confirmé que l’image runtime du cluster en échec contenait un bundle CA plus ancien à cause d’un digest de base piné qui n’avait pas été rebuildé depuis des semaines.
La correction était ennuyeuse : rebuild et redeploy avec l’image de base et les certificats CA mis à jour, puis ajouter une politique pour rebuilder chaque semaine même sans changement de code.
L’incident s’est terminé vite parce que l’équipe avait investi dans les parties non sexy : builds reproductibles et débogage prévisible.
Tâches pratiques : commandes, sorties et décisions (12+)
Ce sont de vraies commandes que vous pouvez lancer aujourd’hui. Chacune est associée à ce que la sortie signifie et à ce que vous faites ensuite. C’est comme ça qu’on arrête de débattre dans les PR
et qu’on commence à prendre des décisions basées sur des preuves.
Tâche 1 : Identifier la lignée de l’image de base
cr0x@server:~$ docker image inspect myapp:latest --format '{{.Id}} {{.RepoTags}}'
sha256:2c9f3d2a6b1f... [myapp:latest]
Ce que ça signifie : Vous avez l’ID immuable de l’image. Les tags mentent ; les IDs non.
Décision : Utilisez l’ID dans les notes d’incident et comparez-le à ce que CI a construit. Si la prod n’exécute pas le même ID, arrêtez le diagnostic et corrigez la dérive de déploiement.
Tâche 2 : Confirmer s’il s’agit de musl ou glibc
cr0x@server:~$ docker run --rm myapp:latest sh -c 'ldd --version 2>&1 | head -n 1'
musl libc (x86_64) Version 1.2.4
Ce que ça signifie : musl libc : vous êtes effectivement en territoire Alpine même si l’image n’est pas littéralement alpine:tag.
Décision : Si vous exécutez des binaires fournisseurs précompilés ou des modules natifs Python/Node, supposez un risque de compatibilité. Envisagez de passer à Debian-slim ou de reconstruire les dépendances correctement.
Tâche 3 : Vérifier les métadonnées de la release OS
cr0x@server:~$ docker run --rm myapp:latest sh -c 'cat /etc/os-release'
PRETTY_NAME="Alpine Linux v3.20"
NAME="Alpine Linux"
ID=alpine
VERSION_ID=3.20.2
Ce que ça signifie : Vous connaissez la distro et la version réelles — utile pour les discussions sur les CVE et la reproductibilité.
Décision : Pincez une version majeure/mineure de la base (ou mieux, un digest) et planifiez des rebuilds. « latest » est la façon d’avoir des mises à jour surprises.
Tâche 4 : Mesurer la taille d’image correctement (compressée et non compressée)
cr0x@server:~$ docker image ls myapp:latest --format 'REPO={{.Repository}} TAG={{.Tag}} SIZE={{.Size}}'
REPO=myapp TAG=latest SIZE=146MB
Ce que ça signifie : Docker affiche une taille influencée par les couches et la compression, pas seulement « ce que vous livrez ».
Décision : Si vous optimisez la taille, concentrez-vous sur le gonflement des dépendances et les couches de build ; ne changez pas la libc en premier geste.
Tâche 5 : Voir ce qui a changé à travers les couches
cr0x@server:~$ docker history myapp:latest --no-trunc | head -n 6
IMAGE CREATED CREATED BY SIZE COMMENT
2c9f3d2a6b1f 2 days ago /bin/sh -c apk add --no-cache curl bash 13.2MB
9a1d4f0c1e22 2 days ago /bin/sh -c adduser -D -g '' app 3.1kB
b17c11d9fba0 2 days ago /bin/sh -c #(nop) COPY file:... in /app 62.4MB
4f2a1c0f2d9e 3 weeks ago /bin/sh -c #(nop) FROM alpine:3.20 5.6MB
Ce que ça signifie : Vous pouvez repérer « on a installé curl et bash en prod » et d’autres hausses lentes.
Décision : Déplacez les outils de débogage vers une image de debug ; gardez le runtime maigre et contrôlé.
Tâche 6 : Vérifier que les certificats CA sont présents (les échecs TLS adorent les CAs manquantes)
cr0x@server:~$ docker run --rm myapp:latest sh -c 'ls -l /etc/ssl/certs | head'
total 84
-rw-r--r-- 1 root root 1477 Oct 2 2025 ca-certificates.crt
drwxr-xr-x 2 root root 4096 Oct 2 2025 java
Ce que ça signifie : Il y a un bundle de certificats ; au moins les bases sont présentes.
Décision : Si le HTTPS sortant échoue, inspectez la date/version du bundle et confirmez que votre runtime le met à jour régulièrement.
Tâche 7 : Reproduire un handshake TLS et inspecter la chaîne
cr0x@server:~$ docker run --rm myapp:latest sh -c 'echo | openssl s_client -connect api.example.com:443 -servername api.example.com 2>/dev/null | openssl x509 -noout -issuer -subject | head -n 2'
issuer=CN = Example Intermediate CA, O = Example Corp
subject=CN = api.example.com
Ce que ça signifie : Vous pouvez voir quel CA est utilisé et si votre conteneur peut exécuter des outils OpenSSL.
Décision : Si votre image de base manque d’outils, ajoutez une image de debug ou embarquez des outils minimaux. N’attendez pas l’incident.
Tâche 8 : Inspecter la configuration du résolveur DNS à l’intérieur du conteneur
cr0x@server:~$ docker run --rm myapp:latest sh -c 'cat /etc/resolv.conf'
nameserver 10.96.0.10
search default.svc.cluster.local svc.cluster.local cluster.local
options ndots:5
Ce que ça signifie : Un ndots élevé + plusieurs domaines de recherche peut créer des requêtes supplémentaires et de la latence.
Décision : Si vous observez des timeouts DNS sous charge, réduisez les domaines de recherche ou ajustez ndots au niveau plateforme ; soyez particulièrement prudent avec les images basées sur musl.
Tâche 9 : Chronométrer les requêtes DNS depuis l’intérieur du conteneur
cr0x@server:~$ docker run --rm myapp:latest sh -c 'time getent hosts api.example.com | head -n 1'
203.0.113.10 api.example.com
real 0m0.043s
user 0m0.000s
sys 0m0.002s
Ce que ça signifie : getent utilise le chemin du résolveur système. C’est une bonne première approximation du comportement DNS.
Décision : Si la résolution est lente, ne profilez pas encore votre appli. Réglez d’abord le DNS ou le comportement du résolveur.
Tâche 10 : Confirmer si un binaire est lié dynamiquement (et à quoi)
cr0x@server:~$ docker run --rm myapp:latest sh -c 'file /app/myapp'
/app/myapp: ELF 64-bit LSB pie executable, x86-64, dynamically linked, interpreter /lib/ld-musl-x86_64.so.1, stripped
Ce que ça signifie : Il est lié dynamiquement à musl. Vous ne pouvez pas prétendre que c’est « Linux portable ». C’est du musl Linux.
Décision : Si vous voulez une portabilité maximale et un comportement prévisible, construisez un binaire ciblant glibc et exécutez-le sur Debian-slim, ou passez au statique complet en connaissant les compromis.
Tâche 11 : Détecter les bibliothèques partagées manquantes à l’exécution
cr0x@server:~$ docker run --rm myapp:latest sh -c 'ldd /app/myapp | tail -n 3'
libpthread.so.0 => /lib/libpthread.so.0 (0x7f1f0c4a0000)
libc.musl-x86_64.so.1 => /lib/ld-musl-x86_64.so.1 (0x7f1f0c6a0000)
Error relocating /app/myapp: __strdup: symbol not found
Ce que ça signifie : Vous avez un décalage de symboles — un classique problème libc/ABI.
Décision : Arrêtez d’insister. Passez le runtime sur Debian-slim (glibc) ou recompilez le binaire pour musl et testez minutieusement.
Tâche 12 : Vérifier si votre image « slim » a encore des shells et des outils
cr0x@server:~$ docker run --rm debian:bookworm-slim sh -c 'command -v bash || echo "bash missing"; command -v curl || echo "curl missing"'
bash missing
curl missing
Ce que ça signifie : Debian-slim n’est pas « Debian complète ». C’est volontairement épuré.
Décision : Si vous avez besoin d’outils, installez-les explicitement ou comptez sur une image de debug. Ne supposez pas qu’ils existent.
Tâche 13 : Comparer l’inventaire des paquets (utile pour le bruit des scanners et la surface d’attaque)
cr0x@server:~$ docker run --rm alpine:3.20 sh -c 'apk info | wc -l'
14
Ce que ça signifie : Très peu de paquets dans une image Alpine de base.
Décision : Si vous voyez ensuite 120+ paquets, vous n’êtes plus « minimal ». Reconsidérez votre Dockerfile et séparez build vs runtime.
Tâche 14 : Valider que vous pouvez patcher l’image de base sans changements de code
cr0x@server:~$ docker build --pull --no-cache -t myapp:rebuild .
...snip...
Successfully built 6c1a1a7f9f19
Successfully tagged myapp:rebuild
Ce que ça signifie : Vous pouvez rebuilder depuis des couches de base fraîches. C’est comme ça que vous récupérez les mises à jour CA et les correctifs de sécurité.
Décision : Si les rebuilds échouent ou prennent des heures, corrigez le pipeline de build. Le patching doit être ennuyeux.
Blague #2 : « On ne peut pas rebuild maintenant » est l’équivalent conteneur de « l’alarme incendie est forte, on peut la couper ? » Ce n’est pas un plan.
Feuille de diagnostic rapide
Quand un service conteneurisé se comporte mal après un changement d’image de base (ou pendant une migration), vous avez besoin d’une séquence rapide et reproductible.
Ne commencez pas par du profiling CPU. Commencez par les éléments ennuyeux qui cassent en premier.
Premier point : confirmer ce qui tourne réellement
- ID d’image en prod vs ce que vous pensez avoir déployé. S’ils diffèrent, arrêtez et corrigez la dérive de déploiement.
- OS + libc à l’intérieur du conteneur : Alpine/musl ou Debian/glibc. Cela réduit immédiatement les modes de défaillance possibles.
- Entrypoint et commande : vérifiez que vous n’avez pas perdu
bashet cassé des scripts, ou changé de répertoire de travail.
Deuxième point : valider les fondamentaux réseau à l’intérieur du conteneur
- Temps de résolution DNS avec
getentpour les noms critiques. - Handshake TLS vers les endpoints clés. Cherchez des erreurs CA et des différences de chaîne.
- Variables proxy (
HTTP_PROXY,NO_PROXY) et configuration du résolveur.
Troisième point : valider les dépendances runtime et le linkage
- Bibliothèques partagées : exécutez
lddet cherchez « not found » ou des erreurs de relocation. - Compatibilité runtime langage : wheels Python, modules natifs Node, stores de certificats Java, paquets de polices.
- Fuseaux horaires / locales si le symptôme est « échoue seulement dans la région X » ou « horodatages incorrects ».
Quatrième point : seulement ensuite mesurer la performance
- Découpage de la latence des requêtes : DNS, connect, TLS, temps en amont.
- CPU vs I/O : confirmez si le changement d’image a produit des chemins d’exécution différents (par ex. backend crypto) ou simplement déplacé les goulots.
- Reproductibilité du build : si la performance a changé, confirmez que l’artifact est identique à part la couche de base.
Erreurs courantes : symptôme → cause racine → correction
1) « Exec format error » ou « not found » alors que le fichier existe clairement
Symptôme : Le conteneur démarre et échoue immédiatement avec « no such file or directory » pour un binaire présent.
Cause racine : Interpréteur/loader manquant ou ABI libc incorrect (binaire lié à glibc sur une image musl), ou mauvaise architecture.
Correction : Exécutez file et ldd dans l’image. Si il attend glibc, passez à Debian-slim ou recompilez pour musl/statique.
2) TLS qui échoue seulement dans certains environnements
Symptôme : « x509: certificate signed by unknown authority » ou échecs de handshake après rotation d’intermédiaires chez le fournisseur.
Cause racine : Bundle CA obsolète ou comportement de store de certificats différent (surtout selon l’image de base).
Correction : Assurez-vous que ca-certificates est installé ; rebuildez les images régulièrement ; validez avec openssl s_client dans une image debug.
3) Timeouts DNS qui disparaissent quand vous retentez
Symptôme : Timeouts sporadiques pour résoudre des noms de services ; les retries aident mais la charge augmente.
Cause racine : Comportement du résolveur / domaines de recherche / ndots interagissant avec musl ; ou DNS de cluster sous pression amplifié par des recherches supplémentaires.
Correction : Mesurez le temps de résolution avec getent ; réduisez les domaines de recherche ; ajustez ndots ; envisagez Debian-slim si le comportement diffère et que vous avez besoin d’un résolveur glibc prévisible.
4) Dépendances Node/Python qui se mettent soudainement à compiler en CI et ralentissent les builds
Symptôme : Pic de temps de build ; nouvelles dépendances de compilateurs et headers ; builds instables.
Cause racine : Les artefacts précompilés ciblent glibc ; sur musl vous retombez sur des builds source.
Correction : Utilisez Debian-slim pour ces charges, ou pinnez et construisez explicitement les artefacts dans une phase builder contrôlée. N’ajoutez pas « apk add build-base » dans les images de production.
5) Scripts shell qui « fonctionnent en slim, échouent en alpine »
Symptôme : Les scripts d’entrypoint échouent sur Alpine avec des comportements différents de sed, grep, date.
Cause racine : Les utilitaires BusyBox diffèrent des GNU coreutils ; les scripts reposent sur des options non POSIX.
Correction : Rendre les scripts conformes POSIX ou installer explicitement les outils GNU nécessaires ; mieux : éviter la glue shell dans les images runtime quand c’est possible.
6) Le nombre de CVE explose après être passé à Debian-slim
Symptôme : Le scanner de sécurité affiche plus de findings ; la direction panique.
Cause racine : Plus de paquets installés et donc plus de metadata qui matchent ; pas automatiquement un risque exploitable plus élevé.
Correction : Trier par exposition runtime et disponibilité des correctifs. Réduire les paquets via des builds multi-stage. Suivre la cadence de rebuild et piner les digests.
7) Images « minimales » qui en réalité ne le sont pas
Symptôme : L’image Alpine finit par être plus grosse que Debian-slim après ajout de toolchains de build, shells et outils de débogage.
Cause racine : Utiliser Alpine comme raccourci au lieu de faire une séparation multi-stage correcte.
Correction : Séparez les étapes build/runtime. Ne gardez dans l’étape finale que les bibliothèques runtime et les artefacts applicatifs.
Listes de vérification / plan étape par étape
Checklist A : Choisir l’image de base (défaut production)
- Listez les dépendances runtime : libs natives, binaires fournisseurs, extensions de langage, besoins SSL/TLS.
- Si une dépendance est un « binaire depuis Internet », par défaut choisissez Debian-slim.
- Si le service est un unique binaire statique et que vous pouvez le prouver : Alpine est acceptable.
- Décidez dès maintenant comment vous déboguerez : image debug, conteneurs debug éphémères, ou outils minimaux dans le runtime.
- Pincez l’image de base par digest ou par une politique de version stricte. Planifiez des rebuilds.
Checklist B : Pipeline de build qui ne vous trahira pas
- Utilisez des builds multi-stage : la phase builder a les compilateurs ; la phase runtime non.
- Rendez les builds déterministes : pinez les dépendances ; évitez les installateurs « curl | sh ».
- Exécutez un test smoke à l’intérieur de l’image construite : lookup DNS, handshake TLS, exécution du binaire.
- Produisez un tag debug qui correspond à la libc runtime (outils musl pour musl, outils glibc pour glibc).
- Rebuildez les images selon un calendrier, même sans changement de code, pour récupérer les mises à jour CA et de sécurité.
Checklist C : Avant de migrer vers Alpine
- Exécutez
lddsur chaque binaire que vous livrez ou téléchargez. - Testez le comportement de résolution DNS sous charge dans un environnement qui correspond au resolv.conf de prod.
- Validez le TLS vers chaque dépendance externe avec des bundles CA actuels.
- Confirmez que votre écosystème langage supporte musl sans builds source surprises.
- Décidez comment vous gérerez les moments « on a besoin de tcpdump » (indice : pas en rebuildant en prod).
FAQ
1) Alpine est-il « plus sûr » parce qu’il est plus petit ?
Plus petit peut réduire le nombre de paquets vulnérables, mais la sécurité dépend surtout de la vitesse de patch, de l’automatisation des rebuilds et des contrôles à l’exécution.
Alpine peut être sécurisé. Debian-slim peut être sécurisé. L’option non sécurisée est celle que vous ne rebuildez pas.
2) Pourquoi les scanners affichent-ils moins de CVE sur Alpine ?
Beaucoup de scanners associent les CVE à des noms/versions de paquets. Moins de paquets signifie moins de correspondances. C’est un signal, pas un verdict. Concentrez-vous sur les chemins exploitables et la capacité de patch.
3) Debian-slim rendra-t-il mon image énorme ?
Pas si vous utilisez des builds multi-stage et gardez les outils de build hors du runtime. De nombreuses images Debian-slim de production sont petites parce que l’app et ses dépendances runtime dominent la taille, pas la base.
4) Puis-je exécuter des applis glibc sur Alpine en installant glibc ?
Vous le pouvez, mais maintenant vous maintenez un environnement hybride avec plus de cas limites. Si votre charge requiert glibc, il est généralement moins coûteux et plus sûr d’exécuter Debian-slim.
5) Qu’en est-il des images distroless ?
Distroless peut être excellent pour réduire la surface runtime, surtout pour des binaires statiques ou des runtimes bien compris. Mais il faut une stratégie de debug. Distroless sans chemin de debug est une dette opérationnelle.
6) Pourquoi les modules natifs Node détestent-ils Alpine ?
Beaucoup de modules Node précompilés sont compilés contre glibc. Sur Alpine (musl), vous compilez souvent depuis les sources, nécessitant compilateurs et headers. Cela augmente le temps et la complexité des builds.
7) Musl est-il « pire » que glibc ?
Non. Il est différent. musl est léger et orienté standard. glibc maximise la compatibilité. En production, la compatibilité avec l’écosystème plus large est souvent l’atout gagnant.
8) Dois-je standardiser sur une seule image de base dans l’entreprise ?
Standardisez sur un cadre de décision, pas sur une seule image. Une image de base unique pour tout semble propre jusqu’à ce que vous tombiez sur un runtime qui a des attentes différentes. Proposez des options approuvées : Debian-slim par défaut, Alpine pour les binaires statiques, distroless quand c’est approprié.
9) À quelle fréquence devons-nous rebuild les images si le code ne change pas ?
Hebdomadaire est un compromis opérationnel courant : assez fréquent pour récupérer les CA et correctifs de sécurité, pas si fréquent pour vous noyer dans le bruit. La bonne réponse dépend de votre posture de risque et de la maturité de votre automatisation.
Étapes suivantes (faites ceci lundi)
- Choisissez un défaut : Debian-slim pour la plupart des services ; Alpine uniquement pour les binaires statiques et les graphes de dépendances strictement contrôlés.
- Ajoutez une variante debug : un tag/image sœur avec des outils réseau et TLS de base qui correspond à la libc runtime.
- Implémentez une cadence de rebuild : rebuild et redeploy des images de base régulièrement ; arrêtez de traiter les couches de base comme « définies et oubliées ».
- Instrumentez les éléments ennuyeux : latence DNS, erreurs TLS et temps de connexion vers les upstream. Ce sont les premières choses qui diffèrent entre images de base.
- Écrivez la règle : « Nous choisissons les images de base pour la compatibilité et l’opérabilité en priorité ; la taille est une optimisation de second ordre. » Mettez-la dans la doc plateforme et appliquez-la en revue.
La meilleure image de base est celle qui n transforme pas des pannes routinières en mystères. Alpine peut être cette image. Debian-slim peut être cette image.
Mais si vous choisissez en vous basant sur des impressions et des mégaoctets, la production vous instruira — à coût élevé.