Skip to Content
CLIDocker multi-stage (Java)

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 :

ImageTaille approximativeContenu
eclipse-temurin:21 (JDK complet)~540 MoJDK complet + outils dev
eclipse-temurin:21-jre-alpine~180 MoJRE uniquement
Multi-stage (Maven + JRE Alpine)~120 MoArtéfact JAR + JRE
Multi-stage (GraalVM native)~30 MoBinaire 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-offline pré-télécharge toutes les dépendances dans .m2 pour éviter les pulls réseau au runtime.
  • COPY pom.xml avant COPY src : la couche RUN mvn dependency:go-offline est 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-certificates

ca-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ègeCorrection
Multi-stage sans AS nomObligatoire : 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 COPYGarder .m2 si --cache-from est configuré, sinon la supprimer dans un RUN dédié en fin de phase build
USER avant HEALTHCHECK sans droits curlcurl 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 -f

Voir aussi