infra / docker-compose
Construire une image avec build
Explication
Ce que vous allez apprendre
- Utiliser
buildpour construire une image localement à partir d'un Dockerfile - Comprendre pourquoi un Dockerfile multi-stage réduit fortement la taille de l'image finale
- Choisir un stage précis à construire grâce à
target(dev vs prod à partir du même fichier) - Passer des arguments de build (
args) sans les coder en dur dans le Dockerfile - Éviter les pièges classiques de cache qui font tourner une ancienne version de l'image
Dans quel contexte ?
L'équipe du projet boutique-api a son propre code Node.js à empaqueter, pas juste une image publique comme nginx. Elle veut aussi que l'image livrée en production ne contienne ni les outils de compilation ni le code source non transpilé — seulement le résultat final, dist/ et les dépendances de production. C'est précisément ce que permet un Dockerfile multi-stage piloté par Compose.
Le point de départ : une image toute faite
Jusqu'ici, les services utilisaient des images déjà publiées, comme nginx ou postgres. Mais dès que ton application a son propre code, une image publique ne suffit plus : il faut fabriquer une image qui contient CE code.
L'instruction qui change tout : build
C'est le rôle de l'instruction build. Au lieu de tirer une image depuis un registre distant, Compose la construit localement, à partir d'un Dockerfile présent dans ton projet. Le résultat est une image utilisable exactement comme les autres.
Une première version simple d'un Dockerfile
Un Dockerfile basique installe les dépendances, copie le code, puis démarre l'application. C'est suffisant pour débuter, mais cette approche a un défaut : l'image finale contient AUSSI tous les outils utilisés pour construire l'application, ce qui la rend inutilement lourde.
Il reste un problème : une image trop lourde
Une image de production qui traîne des outils de compilation est plus longue à transférer, et surtout plus risquée : plus il y a de logiciels installés, plus la surface d'attaque est grande. On aimerait ne garder que le résultat final.
La solution : le multi-stage build
C'est exactement ce que permet un Dockerfile "multi-stage". Imagine un chantier de construction : les échafaudages, les outils et les gravats (l'étage "builder") ne doivent jamais se retrouver dans la maison livrée au client (l'étage "runtime"). On copie uniquement le résultat fini d'un étage vers l'autre.
Choisir précisément quel étage construire
Une fois qu'on a plusieurs étages dans un même Dockerfile, le paramètre target permet de choisir explicitement lequel construire. C'est très pratique pour obtenir, à partir du même fichier, une image "dev" avec des outils de debug et une image "prod" minimale.
| Étage (stage) | Contenu | Utilisé pour |
|---|---|---|
builder | Outils de compilation, code source complet, node_modules de dev | Jamais livré tel quel |
runtime | Uniquement dist/ et les dépendances de production | Image livrée en production |
Bonne pratique
Ordonne toujours tes instructions Dockerfile de la moins changeante à la plus changeante : copie package.json et lance npm ci AVANT de copier le reste du code source. Ainsi, tant que les dépendances ne changent pas, Docker réutilise le cache de cette étape et le build devient nettement plus rapide.
Les pièges à surveiller
Premier piège fréquent : oublier --build après avoir modifié le Dockerfile, ce qui fait que Compose réutilise silencieusement l'ancienne image en cache. Deuxième piège : mal ordonner les instructions COPY et RUN, ce qui empêche Docker d'exploiter son cache et ralentit chaque build. Enfin, sans fichier .dockerignore, des dossiers inutiles comme node_modules ou .git peuvent se retrouver copiés dans l'image par erreur.
Maintenant que tu sais construire tes propres images, la leçon suivante aborde un besoin différent : faire persister des données au-delà de la vie d'un conteneur, avec les volumes.
Commandes & code
Build depuis un Dockerfile
build remplace ou complète image pour construire l'image localement au lieu de la tirer d'un registre.
services:
api:
build: ./api # cherche ./api/Dockerfile
image: myorg/api:dev # tag optionnel donné à l'image construite
ports:
- "3000:3000"
worker:
build:
context: ./worker
dockerfile: Dockerfile.prod
args:
NODE_ENV: production
BUILD_VERSION: "1.4.2"
target: runtime # stage ciblé dans un Dockerfile multi-stage
cache_from:
- myorg/worker:cache# ./api/Dockerfile - multi-stage utilisé par "target: runtime"
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:20-alpine AS runtime
WORKDIR /app
ENV NODE_ENV=production
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
EXPOSE 3000
CMD ["node", "dist/server.js"]# Forcer la reconstruction des images avant de démarrer
docker compose up -d --build
# Reconstruire une image sans démarrer les conteneurs
docker compose build --no-cache api
# Construire toutes les images en parallèle
docker compose build --parallelRésumé
build: ./cheminconstruit depuis un Dockerfile local.argspasse desARGde build,targetchoisit un stage multi-stage.image:avecbuild:donne un tag à l'image construite.--buildforce la reconstruction avantup.
Exercices pratiques
Mission : un Dockerfile à trois étages qui fuite en production
Objectif : Cibler le bon stage d'un Dockerfile multi-stage et réordonner les instructions pour exploiter le cache.
Contexte
Le Dockerfile de boutique-api a évolué : en plus de builder et runtime, un troisième stage debug (avec curl, bash et vim) a été ajouté pour le dépannage local. Le fichier docker-compose.prod.yml ne précise aucun target dans son bloc build:. Par ailleurs, un collègue place COPY . . avant RUN npm ci, ce qui rend chaque build de plus en plus lent.
Diagnostique puis corrige ces deux problèmes.