Introduction
Docker image optimization avec multi-stage builds et Docker BuildKit est essentielle : démarrer un conteneur est facile, l'exploiter de manière cohérente l'est moins. Un guide utile indique quoi configurer, quelle commande prouve que la configuration fonctionne, et à quoi ressemble l'échec lorsque le réglage est manquant ou incorrect.
L'objectif est pratique : relier le sujet à multi-stage Docker builds, Docker BuildKit, Docker layer caching et Dockerfile best practices pour passer du concept à la vérification locale. Vous devez comprendre les composants, les tester en local et éviter les surprises lors de la réutilisation du même schéma en CI/CD ou dans un environnement proche de la production.
Vue d'ensemble du workflow
Pour toute Docker image optimization, partez d'un triptyque : ressource visée (par ex. container image size), changement de configuration (Dockerfile, options de build, variables d'environnement), et commande de preuve (inspection d'images, logs, taille, historique). Adoptez un cycle court : configurez une chose, observez l'état, puis documentez ce qui casse si le réglage est absent, mal configuré, ou exécuté dans un environnement « like prod ».
En pratique, c'est ici que les équipes découvrent des hypothèses implicites : chemins locaux, tags d'image, noms de réseau, fichiers d'environnement, limites de ressources et permissions peuvent diverger entre laptops, runners et hôtes de production. Rendre ces hypothèses explicites évite les surprises.
Vérifications Docker utiles :
- Conteneurs actifs :
docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" - Derniers échecs :
docker logs <container> --tail 100 - Détails (montages, réseaux, env, santé) :
docker inspect <container> - Compose :
docker compose ps,docker compose logs -f <service>,docker compose exec <service> sh
Quand des données sont en jeu, vérifiez où les fichiers sont stockés avant de changer les conteneurs. Un volume nommé comme app_data:/var/lib/app est géré par Docker et se réutilise facilement après rebuild. Un bind mount comme ./data:/var/lib/app pointe vers un répertoire hôte et convient au développement local, mais peut exposer des soucis de permissions, portabilité et sauvegarde si le même chemin n'existe pas ailleurs.
Test de redémarrage « like prod » : arrêtez le conteneur, recréez-le, puis confirmez que l'application retrouve les fichiers attendus. Si les données disparaissent, le service écrivait probablement dans le filesystem du conteneur plutôt que dans un volume ou un mount.
Plan pilote local
Le plan ci-dessous met l'accent sur multi-stage Docker builds, Docker BuildKit, Docker layer caching, Dockerfile best practices, et la prévention des fuites de secrets, avec des exemples reproductibles.
1) Activer Docker BuildKit et prouver son activation
- Activer pour le shell courant :
- macOS/Linux :
export DOCKER_BUILDKIT=1 - Windows PowerShell :
$env: DOCKER_BUILDKIT=1 - Avec Docker Compose (build côté CLI) :
- macOS/Linux :
export COMPOSE_DOCKER_CLI_BUILD=1
Preuve rapide :
- Exécutez un build avec progression détaillée :
docker build --progress=plain -t demo: bk . - Les logs BuildKit affichent des étapes
=> [internal]et des statutsCACHEDlorsque le Docker layer caching est efficace. - Si vous utilisez
RUN --mount=...sans BuildKit, le build échoue avec une erreur liée à l'instructionRUN --mountnon prise en charge.
Astuce : ajoutez en tête de votre Dockerfile la directive de syntaxe moderne pour bénéficier des fonctionnalités avancées :
# syntax=docker/dockerfile:1.7
2) Dockerfile multi-stage (exemple Go) et taille minimale
Objectif : compiler dans une image « builder », puis ne copier dans l'image finale que les artefacts nécessaires.
Exemple :
# syntax=docker/dockerfile:1.7
ARG GO_VERSION=1.22
FROM golang:${GO_VERSION}-alpine AS builder
WORKDIR /src
# Caches dédiés pour accélérer les builds incrémentaux
COPY go.mod go.sum ./
RUN --mount=type=cache, target=/go/pkg/mod \
go mod download
COPY . .
RUN --mount=type=cache, target=/root/.cache/go-build \
CGO_ENABLED=0 go build -o /out/app ./cmd/app
FROM alpine:3.20 AS runtime
# Bonne pratique : exécuter en utilisateur non-root
RUN adduser -D -u 10001 app
COPY --from=builder /out/app /usr/local/bin/app
USER app
EXPOSE 8080
ENTRYPOINT ["app"]
Commandes de preuve :
- Construire :
docker build -t myapp: multi-stage . - Taille de l'image :
docker images | grep myapp - Historique des couches :
docker history myapp: multi-stage
Échec typique si mal configuré :
- Sans multi-stage, la container image size grossit (outil de build, cache et sources copiés). L'historique montrera des couches volumineuses provenant de la base « builder ».
3) Structurer le Docker layer caching
Le cache devient efficace si le Dockerfile est ordonné du plus stable au plus changeant.
- Placez
COPY go.mod go.sumpuisgo mod downloadavantCOPY . .pour éviter d'invalider le cache à chaque changement de code. - Utilisez les mounts de cache BuildKit pour les outils (ci-dessus :
/go/pkg/mod,/root/.cache/go-build). - Évitez les RUN monolithiques qui mêlent dépendances et sources très variables.
Preuve :
- Faites deux builds successifs sans modifications : la seconde exécution doit afficher des étapes
CACHED. - Modifiez un fichier source : seules les couches postérieures au
COPY . .devraient se reconstruire.
4) Éviter les fuites de secrets
Bonnes pratiques Dockerfile best practices :
.dockerignorepour exclure.env, clés privées, dossiers de build locaux, etc. Exemple minimal :
# .dockerignore
.git
node_modules
.env*
id_rsa
*.pem
**/.DS_Store
- Ne pas copier l'intégralité du contexte si inutile ; préférez des
COPYciblés. - Utiliser les secrets de BuildKit pour l'accès transitoire à des tokens lors du build :
# Exemple générique pendant le build
RUN --mount=type=secret, id=my_token \
sh -c 'TOKEN=$(cat /run/secrets/my_token); echo "Using token for a private fetch" >/dev/null'
Build :
docker build --secret id=my_token, src=./.my_token -t myapp: secure .
Les secrets ne se retrouvent pas dans les couches finales si utilisés via --mount=type=secret.
5) Test local avec Docker Compose et persistance
Un mini-scénario « like prod » permet de valider volumes, réseau et redémarrages.
compose.yaml :
services:
app:
build:
context: .
dockerfile: Dockerfile
image: myapp: multi-stage
ports:
- "8080:8080"
volumes:
- app_data:/var/lib/app
restart: unless-stopped
volumes:
app_data:
Commandes utiles :
- Démarrer :
docker compose up -d - Status/ports :
docker compose ps - Logs en direct :
docker compose logs -f app - Debug shell :
docker compose exec app sh
Test de redémarrage :
docker compose stop appdocker compose up -d --build- Vérifiez que les fichiers attendus sous
/var/lib/appsont toujours présents (volume nommé).
Échec typique : si les données disparaissent, l'application écrivait probablement dans le filesystem du conteneur au lieu du volume.
6) Contrôles de production et troubleshooting
- Tags explicites (éviter
latest) pour prédictibilité en CI/CD. - USER non-root quand possible, permissions minimales.
- Ajoutez un
HEALTHCHECKsi pertinent pour la supervision. - Limitez l'empreinte des couches : nettoyez les caches d'APK/APT dans la même couche RUN où ils sont installés.
- Diagnostiquez une image lourde avec :
docker images(vue d'ensemble)docker history <image>(couches et tailles)- Logs et inspect :
docker logs <container> --tail 100docker inspect <container>(montages, réseaux, env, health)
Symptômes courants et pistes :
- BuildKit inactif : absence des étapes
=> [internal]et erreur surRUN --mount. ActivezDOCKER_BUILDKIT=1et la directive# syntax=.... - Cache inefficace : ordre des
COPYmal optimisé ; re-séparez dépendances stables et sources volatiles. - Fichiers sensibles présents :
.dockerignoreincomplet ; vérifiez aussi les chemins deCOPYglobaux. - Écarts local/CI : variables d'environnement ou chemins différents ; rendez ces hypothèses explicites dans Compose et documentation.
Conclusion
Docker image optimization avec multi-stage builds et Docker BuildKit fonctionne au mieux quand la configuration est systématiquement testée. La voie la plus sûre : garder des exemples petits, exécuter les commandes localement et confirmer le comportement attendu avant d'ajouter d'autres services ou de l'automatisation.
Prochaine étape : choisissez un service et documentez exactement les commandes pour builder, lancer, inspecter, stopper et recréer. Comparez ensuite avec les exigences liées à Docker production operations, Dockerfile best practices et Docker security pour que l'implémentation s'intègre au modèle d'exploitation global.
Un workflow fiable rend l'échec visible : des logs faciles à trouver, des données persistantes qui survivent aux rebuilds, et un comportement local suffisamment proche de la production pour détecter tôt les erreurs. Cela réduit la container image size, améliore la rapidité des builds grâce au Docker layer caching, et prépare des conteneurs réellement prêts pour la production.