Docker pour les devs - 08 - Choisir son image de base

Alpine, slim, distroless, Bun, Python : quelle image de base choisir, les pièges d'Alpine, et un tableau comparatif des tailles.

  1. 01 Docker pour les devs - 00 - Pourquoi Docker change tout
  2. 02 Docker pour les devs - 01 - Containers vs VMs
  3. 03 Docker pour les devs - 02 - L'architecture de Docker
  4. 04 Docker pour les devs - 03 - Docker Desktop, Engine et alternatives
  5. 05 Docker pour les devs - 04 - Écrire un Dockerfile
  6. 06 Docker pour les devs - 05 - Layers et cache
  7. 07 Docker pour les devs - 06 - Le .dockerignore
  8. 08 Docker pour les devs - 07 - Multi-stage builds
  9. 09 Docker pour les devs - 08 - Choisir son image de base
  10. 10 Docker pour les devs - 09 - Docker Compose, les bases
  11. 11 Docker pour les devs - 10 - Docker Compose avance
  12. 12 Docker pour les devs - 11 - Networking Docker, les bases
  13. 13 Docker pour les devs - 12 - Networking Docker avance
  14. 14 Docker pour les devs - 13 - Volumes et persistance
  15. 15 Docker pour les devs - 14 - Variables d'environnement et secrets
  16. 16 Docker pour les devs - 15 - Permissions et utilisateurs
  17. 17 Docker pour les devs - 16 - Docker et monorepo
  18. 18 Docker pour les devs - 17 - Dev vs Prod
  19. 19 Docker pour les devs - 18 - ENTRYPOINT, CMD et scripts d'initialisation
  20. 20 Docker pour les devs - 19 - Debugger ses conteneurs
  21. 21 Docker pour les devs - 20 - Bases de donnees dans Docker
  22. 22 Docker pour les devs - 21 - Sauvegardes et restauration
  23. 23 Docker pour les devs - 22 - Conteneuriser un frontend
  24. 24 Docker pour les devs - 23 - Sécurité des conteneurs
  25. 25 Docker pour les devs - 24 - Optimisation des images
  26. 26 Docker pour les devs - 25 - Builds multi-platform
  27. 27 Docker pour les devs - 26 - Limiter les ressources de tes conteneurs
  28. 28 Docker pour les devs - 27 - Gerer les logs comme un adulte
  29. 29 Docker pour les devs - 28 - Healthchecks et restart policies
  30. 30 Docker pour les devs - 29 - Nettoyer Docker avant qu'il mange ton disque
  31. 31 Docker pour les devs - 30 - Registries et stratégie de tags
  32. 32 Docker pour les devs - 31 - Docker en CI/CD
  33. 33 Docker pour les devs - 32 - Au-dela de Compose
  34. 34 Docker pour les devs - 33 - Glossaire Docker de A a Z

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


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 :

  1. Tu deploies du Go compile statiquement ? Utilise scratch.
  2. Tu as des contraintes de sécurité fortes ? Utilise distroless.
  3. Tu utilises Bun ? Utilise oven/bun:slim.
  4. Tu utilises Node et tu n'as pas de modules natifs ? Utilise node:slim.
  5. Tu utilises Node avec des modules natifs ? Utilise node:slim en prod, node:full en stage de build.
  6. Tu utilises Python ? Utilise python:slim. Evite Alpine sauf si tu sais ce que tu fais.
  7. 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é

  • slim est 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, pas node:latest)

Precedent : 07 - Multi-stage builds | Suivant : 09 - Compose - les bases

Sources

Réservez un audit gratuit de 30 minutes. Je vous montre concrètement ce qu'on peut automatiser.