Que sont les constructions de Docker multi-stage?

Chaque instruction commence une nouvelle étape, qui peut avoir sa propre image de base, dépendances et commandes. Les artefacts peuvent être copiés de façon sélective d'une étape à l'autre, tandis que l'image finale ne conserve que ce qui est strictement nécessaire pour exécuter l'application. Cette approche élimine le besoin de chaîner manuellement Dockerfiles ou de s'appuyer sur des scripts de construction complexes, et elle réduit considérablement la taille de l'image en excluant les compilateurs, les bibliothèques de développement et les fichiers intermédiaires.

Dans une construction typique à un seul étage, un développeur installe tous les outils de construction (p. ex., compilateurs, gestionnaires de paquets, cadres de test) dans la même image qui sera utilisée pour la production. Cela gonfle l'image et augmente la surface d'attaque. Les constructions à plusieurs étages résolvent cela en utilisant une image épaisse et riche en fonctionnalités pour la compilation, puis en copieant uniquement les artefacts résultants dans une image minimale d'exécution comme ou .

Avantages des constructions multi-étages

Les avantages de l'adoption de constructions multi-étapes vont au-delà de la simple réduction de taille. Ils ont un impact profond sur la sécurité, la maintenance et la vitesse de déploiement.

1. Taille réduite de l'image

Par exemple, une application Node.js construite en utilisant l'image complète (plus de 300 Mo) peut être réduite à moins de 20 Mo en copiant seulement le dossier construit dans une base . Cette économie de stockage se traduit directement par des temps de tirage plus rapides, une bande passante réseau moins grande et des coûts de registre plus faibles.

2. Amélioration de la sécurité

Chaque paquet ou outil installé dans une image conteneur est une vulnérabilité potentielle. Les compilations multi-étapes vous permettent d'exclure les compilateurs, les débogueurs et les bibliothèques de développement de l'image finale, réduisant ainsi considérablement la surface d'attaque. Vous pouvez même utiliser des images comme ou pour l'étape d'exécution, qui ne contiennent que le minimum pour exécuter le binaire de l'application.

3. Processus de construction simplifié

Toutes les étapes de construction sont définies dans un seul fichier Dockerfile, rendant le processus autonome et facile à version. Les pipelines CI/CD bénéficient d'un seul point d'entrée : le fichier Dockerfile. Il n'est pas nécessaire de maintenir des scripts de construction séparés ou des étapes de nettoyage manuel.

4. Reproductibilité et cohérence accrues

Comme la compilation entière est capturée dans un fichier Dockerfile, tout développeur ou système peut reproduire exactement les mêmes couches. L'utilisation de balises de version spécifiques pour les images de base garantit en outre des constructions cohérentes entre les environnements.

Construire un fichier Docker multi-étapes : étape par étape

Cette démarche couvre la création d'un fichier Dockerfile multi-étapes prêt à la production pour une application Node.js et React. Les mêmes principes s'appliquent à tout langage compilé.

1. Planifiez vos étapes

Avant d'écrire le code, cartographiez les étapes dont vous avez besoin. Une construction multi-étapes typique comporte au moins deux étapes :

  • Builder stage – installe tous les outils de construction, installe les dépendances et exécute la commande build.
  • Stage de runtime – utilise une image de base minimale, copie uniquement les artefacts construits à partir de la scène du constructeur, et définit le comportement d'exécution.

Pour les projets complexes, vous pouvez ajouter des étapes intermédiaires pour les tests, l'analyse statique ou la compression des actifs.

2. Écrire l'étape du constructeur

Commencez par une image de base qui comprend la chaîne d'outils requise. Utilisez des étapes nommées avec pour les référencer plus tard.

FROM node:14-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build

Points clés:

  • Utilisez pour une installation déterministe et plus rapide.
  • Gardez des commandes en couches séparées lorsque cela est possible pour tirer parti de la mise en cache.
  • Installez des outils de construction (comme TypeScript, webpack) dans cette étape seulement.

3. Ajouter une étape d ' essai intermédiaire (facultatif)

Pour faire respecter la qualité du code, ajoutez une étape qui exécute des tests. Cette étape peut réutiliser l'image du constructeur ou installer des outils supplémentaires.

FROM builder AS test
RUN npm run test

Vous pouvez exécuter cette étape dans votre pipeline CI avec pour attraper les échecs tôt sans construire l'image finale entière.

4. Définir l'étape de l'exécution

Pour une application React, l'image d'exécution peut être un serveur Nginx. Pour une API backend, il peut s'agir d'une image de base distroless ou d'un alpin minimal avec l'exécution Node.js. Copier seulement les artefacts essentiels en utilisant .

FROM nginx:alpine
COPY --from=builder /app/build /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

Si vous avez besoin du Node.js runtime, évitez de copier du constructeur; au lieu de réinstaller les dépendances de production dans l'étape d'exécution:

FROM node:14-alpine AS runtime
WORKDIR /app
COPY --from=builder /app/dist ./dist
RUN npm ci --only=production
EXPOSE 3000
CMD ["node", "dist/server.js"]

5. Construire et tester l'image

Construisez l'image finale en utilisant la commande standard :

docker build -t myapp:latest .

Pour vérifier la taille, exécutez et comparez-la à une construction en une seule étape. Exécutez le conteneur et confirmez que l'application répond correctement :

docker run -d -p 8080:80 myapp:latest
curl http://localhost:8080

Meilleures pratiques pour les constructions multi-étages

  • Utiliser des étiquettes d'image de base spécifiques – éviter pour éviter les surprises. Préférez ou .
  • Optimiser la mise en cache des couches – copie et avant le reste du code source de sorte que la couche ne soit invalidée que lorsque les dépendances changent.
  • Leverage buildKit – activer BuildKit avec pour des constructions plus rapides, un cache en ligne et un meilleur parallélisme.
  • Créez plusieurs étapes finales pour différents environnements – par exemple, une étape de développement avec des outils de débogage et une étape de production avec une image de base durcie.
  • ]Pour le développement, utilisez pour les constructions – en développement, vous pouvez vous arrêter au stade pour obtenir des cartes de recharge en direct et des cartes sources, puis reconstruire avec pour la production.
  • Garder les secrets hors des images – utilisez le drapeau Docker BuildKit=] si vous devez passer des identifiants pendant la construction; ne jamais les inclure dans l'image finale.

Les modèles et les cas d'utilisation courants

Langues compilées (Go, Rust, C++)

Pour les binaires statiques, l'étape d'exécution peut utiliser (image de base vide). Seuls le binaire et peut-être un fichier de configuration sont copiés. Exemple pour Go:

FROM golang:1.20-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -o myapp .

FROM scratch
COPY --from=builder /app/myapp /myapp
ENTRYPOINT ["/myapp"]

Applications Python

Utilisez une étape de constructeur avec pour installer des dépendances et compiler des extensions C, puis copiez seulement les paquets installés à une étape d'exécution:

FROM python:3.11-slim AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --user -r requirements.txt
COPY . .

FROM python:3.11-slim
COPY --from=builder /root/.local /root/.local
COPY --from=builder /app /app
ENV PATH=/root/.local/bin:$PATH
CMD ["python", "app.py"]

Frontend avec serveur API

Construisez à la fois frontend et backend dans un fichier Dockerfile. Utilisez des étapes de construction distinctes pour chaque, puis copiez les deux artefacts dans une image d'exécution unique :

FROM node:14-alpine AS frontend-builder
WORKDIR /app
COPY frontend/package*.json ./
RUN npm ci
COPY frontend/ .
RUN npm run build

FROM node:14-alpine AS api-builder
WORKDIR /app
COPY api/package*.json ./
RUN npm ci
COPY api/ .
RUN npm run build

FROM node:14-alpine
WORKDIR /app
COPY --from=frontend-builder /app/build ./public
COPY --from=api-builder /app/dist ./dist
RUN npm ci --only=production
EXPOSE 3000
CMD ["node", "dist/server.js"]

Dépannage de constructions multi-étages

  • Layer cacher ne fonctionne pas – s'assurer que commande les dépendances d'ordre avant le code source. Utilisez pour exclure les fichiers inutiles.
  • Artefact non trouvé – vérifier les chemins dans l'instruction . L'étape du constructeur doit produire la sortie à l'emplacement spécifié. Utilisez pour déboguer.
  • Secret fuite[ – ne jamais copier des répertoires entiers qui pourraient contenir ou . Copier explicitement seulement les fichiers nécessaires.
  • Les plus grandes images finales malgré plusieurs étapes – vérifiez si vous copiez accidentellement ou toute la source. Utilisez pour voir les tailles de calque.

Conclusion

Les constructions Docker multi-étapes sont une pierre angulaire de la conteneurisation moderne. Elles vous permettent d'expédier des images maigres et sécurisées tout en gardant le processus de construction simple et documenté dans un seul fichier Docker. En séparant les préoccupations entre build et runtime, vous pouvez réduire considérablement la taille de l'image, améliorer la sécurité et rationaliser les pipelines CI/CD. Les techniques présentées ici s'appliquent à presque n'importe quelle pile, que vous construisiez des applications Node.js, Go, Python, Java ou frontend. Commencez par convertir vos fichiers Docker existants en constructions multi-étapes, et vous verrez immédiatement des améliorations dans la vitesse de construction, l'efficacité de déploiement et les coûts d'infrastructure globaux.

Pour plus de détails, consultez le document officiel de construction en plusieurs étapes [ et le guide des meilleures pratiques . Des exemples du monde réel sont également disponibles dans la documentation de la bibliothèque .