08 - Choisir son image de base
Ce que tu vas apprendre
- Les variantes d'images officielles : latest, alpine, slim, bookworm
- Ce que sont les images distroless et quand les utiliser
- Les pièges d'Alpine (musl vs glibc)
- Un tableau comparatif des tailles par langage
- La compatibilité ARM (Apple Silicon, Graviton)
- Comment choisir en fonction de ton contexte
Prerequisites
- Avoir lu l'article 04 - Dockerfile et l'article 07 - Multi-stage builds
- Comprendre la notion de couches Docker
Le bug invisible d'Alpine
J'utilisais node:alpine partout. Les images etaient petites, les builds rapides, tout allait bien. Jusqu'au jour ou un calcul financier a renvoye un résultat faux en staging. Le meme code, avec les memes donnees, donnait un résultat différent sur ma machine (Debian) et dans le conteneur (Alpine).
Le coupable : musl libc. Alpine utilise musl au lieu de glibc. Les deux implementations de la librairie C standard ont des différences subtiles dans le traitement des flottants et des locales. Ca ne se voit presque jamais. Sauf quand ca se voit.
Depuis, je reflechis avant de mettre Alpine partout.
Les variantes d'images officielles
La plupart des images officielles (Node, Python, Go, Bun) proposent plusieurs variantes. Prenons Node.js comme exemple :
node:22 (ou node:latest)
C'est l'image complète, basee sur Debian Bookworm. Elle inclut l'ensemble des outils système : gcc, make, git, curl, wget, et les librairies de développement.
bashdocker pull node:22
# Taille : ~350 Mo (compresse), ~1.1 Go (sur disque)
Quand l'utiliser : en stage de build quand tu as besoin de compiler des modules natifs (bcrypt, sharp, canvas).
node:22-slim
Image basee sur Debian Bookworm, mais sans les outils de compilation. Le strict minimum pour faire tourner Node.js.
bashdocker pull node:22-slim
# Taille : ~70 Mo (compresse), ~200 Mo (sur disque)
Quand l'utiliser : en stage de production pour la plupart des applications. C'est le bon choix par défaut.
node:22-alpine
Image basee sur Alpine Linux, une distribution ultra-legere. Elle utilise musl libc au lieu de glibc et ash au lieu de bash.
bashdocker pull node:22-alpine
# Taille : ~45 Mo (compresse), ~130 Mo (sur disque)
Quand l'utiliser : quand la taille est la priorité absolue et que tu ne depends pas de modules natifs compiles avec glibc.
Tableau comparatif des tailles
| Image | Compresse | Sur disque |
|---|---|---|
| node:22 | 350 Mo | 1.1 Go |
| node:22-slim | 70 Mo | 200 Mo |
| node:22-alpine | 45 Mo | 130 Mo |
| python:3.12 | 360 Mo | 1.0 Go |
| python:3.12-slim | 50 Mo | 150 Mo |
| python:3.12-alpine | 20 Mo | 60 Mo |
| golang:1.22 | 260 Mo | 800 Mo |
| golang:1.22-alpine | 80 Mo | 240 Mo |
| oven/bun:1.1 | 100 Mo | 300 Mo |
| oven/bun:1.1-slim | 55 Mo | 160 Mo |
| gcr.io/distroless/nodejs22 | 40 Mo | 120 Mo |
| scratch | 0 Mo | 0 Mo |
Les chiffres varient selon les versions. L'ordre de grandeur reste le meme.
Le piège d'Alpine : musl vs glibc
Alpine utilise musl libc au lieu de glibc (GNU C Library). Presque tous les binaires Linux sont compiles contre glibc. Quand tu installes un module Node natif ou un binaire precompile, il attend glibc.
Les symptomes :
Error: Error loading shared library ld-linux-x86-64.so.2
Ou pire, pas d'erreur mais un comportement différent. Les différences connues :
- DNS : musl resout le DNS differemment. Certains cas de résolution echouent ou sont plus lents.
- Locales : musl ne supporte pas les locales de la meme facon.
Intl,toLocaleString(), le tri de chaînes peuvent donner des résultats différents. - Precision numérique : des différences marginales dans les calculs flottants.
- Threads : le comportement des threads POSIX différé sur certains edge cases.
Pour la plupart des applications web, ca ne pose pas de problème. Mais si tu fais du calcul precis, du traitement de texte avec des locales, ou que tu utilises des modules natifs exotiques, teste sur Alpine avant de t'engager.
Les images distroless
Google maintient les images "distroless" : des images qui contiennent le runtime et rien d'autre. Pas de shell, pas de gestionnaire de paquets, pas de ls, pas de cat.
dockerfile# Build
FROM node:22 AS builder
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --omit=dev
COPY dist/ ./dist/
# Production
FROM gcr.io/distroless/nodejs22-debian12
WORKDIR /app
COPY --from=builder /app .
CMD ["dist/index.js"]
Les avantages :
- Surface d'attaque minimale (pas de shell pour un attaquant)
- Taille réduite
- Conforme aux politiques de sécurité strictes
Les inconvenients :
- Impossible de se connecter au conteneur avec
docker exec sh - Le debug en production est plus difficile
- Pas de gestionnaire de paquets pour installer des outils
En pratique, les images distroless sont un bon choix pour les services en production qui n'ont pas besoin d'etre debugges en live. Pour le dev, c'est impraticable.
L'image Bun officielle
Bun propose ses propres images basees sur Debian :
dockerfile# Image complete
FROM oven/bun:1.1
# Image slim
FROM oven/bun:1.1-slim
# Image alpine
FROM oven/bun:1.1-alpine
L'avantage de Bun : le runtime est un seul binaire. Pas de node_modules pour le runtime lui-meme. Les images sont naturellement plus petites que les equivalentes Node.
dockerfileFROM oven/bun:1.1-slim
WORKDIR /app
COPY package.json bun.lockb ./
RUN bun install --frozen-lockfile --production
COPY src/ ./src/
USER bun
EXPOSE 3000
CMD ["bun", "run", "src/index.ts"]
Bun peut exécuter TypeScript directement. Pas besoin de stage de compilation pour le TS. Ca simplifie le Dockerfile.
Python : le cas slim
Python a le meme schema que Node :
dockerfile# Dev / build (avec gcc, headers)
FROM python:3.12
# Production
FROM python:3.12-slim
Pour Python, slim est presque toujours le bon choix en production. Alpine pose encore plus de problèmes qu'avec Node, parce que beaucoup de packages Python (numpy, pandas, scipy) dependent de librairies C compilees contre glibc.
dockerfileFROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
USER nobody
CMD ["python", "app.py"]
ARM et multi-architecture
Depuis les Mac Apple Silicon (M1, M2, M3) et les instances AWS Graviton, la compatibilité ARM est devenue un sujet. Les images officielles supportent généralement amd64 et arm64 :
bash# Verifier les architectures disponibles
docker manifest inspect node:22-slim | grep architecture
Si tu construis sur un Mac M1 et que tu deploies sur un serveur x86 :
bash# Construire pour une architecture specifique
docker build --platform linux/amd64 -t mon-app .
# Construire pour les deux
docker buildx build --platform linux/amd64,linux/arm64 -t mon-app .
Alpine supporte bien ARM. Les images distroless aussi. Les problèmes de compatibilité ARM viennent surtout des modules natifs qui n'ont pas de binaires precompiles pour arm64.
Comment choisir
Mon arbre de décision :
- Tu deploies du Go compile statiquement ? Utilise
scratch. - Tu as des contraintes de sécurité fortes ? Utilise distroless.
- Tu utilises Bun ? Utilise
oven/bun:slim. - Tu utilises Node et tu n'as pas de modules natifs ? Utilise
node:slim. - Tu utilises Node avec des modules natifs ? Utilise
node:slimen prod,node:fullen stage de build. - Tu utilises Python ? Utilise
python:slim. Evite Alpine sauf si tu sais ce que tu fais. - La taille est critique et tu as teste sur Alpine ? Utilise Alpine.
Le défaut raisonnable, c'est slim. C'est le meilleur compromis entre taille, compatibilité, et facilite de debug.
Sur paltemps.fr, on utilise oven/bun:slim pour les services Bun et node:22-slim pour les services Node. On a abandonne Alpine apres le bug de calcul que j'ai mentionne au début.
Verifier la taille de tes images
bash# Taille de toutes tes images
docker images --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}"
# Taille detaillee
docker image inspect mon-app --format '{{.Size}}' | numfmt --to=iec
Compare régulièrement. Une image qui grossit sans raison, c'est souvent un COPY . . qui embarque des fichiers inattendus. Relis l'article 06 sur le .dockerignore.
Résumé
slimest le bon choix par défaut pour la production (Debian, compatible glibc)- Alpine est plus petit mais utilise musl, ce qui cause des bugs subtils avec certains modules
- Les images distroless offrent la meilleure sécurité mais compliquent le debug
- Bun et Go produisent les images les plus petites grâce à leur modèle de compilation
- Verifie la compatibilité ARM si tu développés sur Mac et deploies sur x86 (ou inversement)
- Epingle toujours une version precise (
node:22-slim, pasnode:latest)
Precedent : 07 - Multi-stage builds | Suivant : 09 - Compose - les bases