Optimiser la taille des images Docker Java en séparant la phase de build de la phase d’exécution.
Pourquoi un multi-stage build ?
Un build Java classique produit des images de 500 Mo à 1 Go. Le pattern multi-stage sépare le compilateur du runtime :
| Image | Taille approximative | Contenu |
|---|---|---|
eclipse-temurin:21 (JDK complet) | ~540 Mo | JDK complet + outils dev |
eclipse-temurin:21-jre-alpine | ~180 Mo | JRE uniquement |
| Multi-stage (Maven + JRE Alpine) | ~120 Mo | Artéfact JAR + JRE |
| Multi-stage (GraalVM native) | ~30 Mo | Binaire compilé + musl |
Le gain est significatif pour le Pull/push, les scans de vulnérabilités, et les temps de déploiement.
Template Maven (Spring Boot)
# --- Build ---
FROM maven:3.9-eclipse-temurin-21 AS build
WORKDIR /build
# Couche de cache Maven : ne rebuild que si pom.xml change
COPY pom.xml ./
RUN mvn dependency:go-offline -B
# Cache Gradle/Maven local via volume
COPY src ./src
RUN mvn package -DskipTests -B
# --- Runtime ---
FROM eclipse-temurin:21-jre-alpine
WORKDIR /app
# curl pour la vérification de santé des déploiements
RUN apk add --no-cache curl ca-certificates
COPY --from=build /build/target/*.jar app.jar
EXPOSE 8080
USER 1000
HEALTHCHECK --interval=30s --timeout=3s \
CMD curl -f http://localhost:8080/actuator/health || exit 1
ENTRYPOINT ["java", "-jar", "app.jar"]Points clés :
mvn dependency:go-offlinepré-télécharge toutes les dépendances dans.m2pour éviter les pulls réseau au runtime.COPY pom.xmlavantCOPY src: la coucheRUN mvn dependency:go-offlineest mise en cache quand seul le code source change.- L’utilisateur non-root (
USER 1000) réduit la surface d’attaque.
Template Gradle (Micronaut)
# --- Build ---
FROM eclipse-temurin:21 AS build
WORKDIR /build
# Cache Gradle dans un volume externe pour éviter le rebuild complet
COPY gradlew .
COPY settings.gradle* build.gradle* ./
COPY src ./src
RUN chmod +x gradlew
RUN ./gradlew build -x test --no-daemon
# --- Runtime ---
FROM eclipse-temurin:21-jre-alpine
WORKDIR /app
RUN apk add --no-cache curl ca-certificates
COPY --from=build /build/build/libs/*.jar app.jar
EXPOSE 8080
USER 1000
HEALTHCHECK --interval=30s --timeout=3s \
CMD curl -f http://localhost:8080/health || exit 1
ENTRYPOINT ["java", "-jar", "app.jar"]Avec cache Gradle persistant (recommandé en CI) :
docker build \
--cache-from type=local,src=$HOME/.gradle/caches \
--cache-to type=local,dest=$HOME/.gradle/caches,new=true \
-t mon-app .Ce flag synchronise le cache Gradle entre deux builds sans réinstaller les dépendances, réduisant le build de ~2 min à ~30 s.
Utilitaires essentiels dans l’image runtime
Même avec un JRE Alpine, ajouter curl et ca-certificates simplifie le debugging et la vérification de santé :
# Dans la phase runtime, après WORKDIR
RUN apk add --no-cache curl ca-certificatesca-certificates est nécessaire pour les appels HTTPS (API externes, registre Docker). curl permet les probes Kubernetes liveness/readiness et le debugging docker exec.
Pièges courants
| Piège | Correction |
|---|---|
Multi-stage sans AS nom | Obligatoire : AS builder doit être référencé par --from=builder |
ENTRYPOINT en format shell "java -jar app.jar" | Format exec ["java", "-jar", "app.jar"] pour la propagation des signaux SIGTERM |
RUN rm -rf ~/.m2/repository dans la même couche que COPY | Garder .m2 si --cache-from est configuré, sinon la supprimer dans un RUN dédié en fin de phase build |
USER avant HEALTHCHECK sans droits curl | curl s’exécute sous l’utilisateur défini par USER — vérifier les permissions |
Vérification
# Taille de l'image finale
docker images mon-app
# Vérifier l'utilisateur d'exécution
docker run --rm mon-app id
# Tester le HEALTHCHECK
docker inspect --format='{{.State.Health.Status}}' mon-app
# Vérifier curl est bien installé
docker run --rm mon-app curl --version
# Purger les images intermédiaires (sans nom)
docker image prune -fVoir aussi
- Docker CLI — commandes runtime (run, exec, logs, inspect)
- Dockerfile — Bonnes pratiques — caching, .dockerignore, sécurité
- Docker Compose — orchestration multi-containers