La meilleure raison d'utiliser des constructions Docker à plusieurs étapes n'est pas l'esthétique. C'est la discipline.
Les images de production devraient contenir l'application et le runtime nécessaire à son exécution. Elles ne devraient pas également contenir :
- des compilateurs
- des caches du gestionnaire de paquets
- des outils de test
- des fichiers source qui ne sont utiles qu'au moment de la construction
Les constructions à plusieurs étapes rendent cette séparation explicite.
Le Modèle
Construisez dans une étape, exécutez dans une autre :
FROM node:22-bookworm AS build
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN corepack enable && pnpm install --frozen-lockfile
COPY . .
RUN pnpm build
RUN pnpm prune --prod
FROM node:22-alpine AS runtime
WORKDIR /app
COPY --from=build /app/dist ./dist
COPY --from=build /app/node_modules ./node_modules
COPY --from=build /app/package.json ./package.json
USER node
CMD ["node", "dist/server.js"]
Cela vous donne une séparation claire entre "ce qui est nécessaire à la construction" et "ce qui est nécessaire à l'exécution."
Le Dilemme Alpine
Alpine peut être une bonne image de runtime. Ce n'est pas automatiquement la bonne image.
Vous devez toujours réfléchir à :
- des dépendances natives
- la compatibilité de libc
- les besoins en débogage
- la provenance de l'image
Parfois, une image mince basée sur Debian est un meilleur compromis opérationnel. Le principe est d'expédier moins, pas de cultiver un modèle d'image de base spécifique.
Ce que cela vous apporte
Bien fait, les constructions à plusieurs étapes améliorent :
- la taille de l'image
- le temps de démarrage à froid et de téléchargement
- la surface d'attaque
- la clarté autour des dépendances du runtime
C'est pourquoi il vaut la peine de le faire même lorsque la différence de taille d'image est modeste.
Lectures complémentaires