Retour aux articles

Créer des conteneurs Docker multi-étapes efficaces pour les applications Java Spring Boot : Sécurité et Taille

Créer des conteneurs Docker multi-étapes efficaces pour les applications Java Spring Boot : Sécurité et Taille | Laty Gueye Samba - Développeur Full Stack Dakar Sénégal, Expert Java Spring Boot Angular
```html

Créer des conteneurs Docker multi-étapes efficaces pour les applications Java Spring Boot : sécurité et taille

Les conteneurs Docker multi-étapes permettent de construire des images plus petites, plus sûres et plus reproductibles pour les applications Java Spring Boot. En séparant les étapes de compilation et d’exécution, il devient possible de réduire drastiquement l’empreinte mémoire/stockage et d’éviter d’inclure des outils de build dans le conteneur final.

Pourquoi une approche multi-étapes ?

Une image Spring Boot “monolithique” inclut souvent un JDK, un outil de build (Maven/Gradle), des dépendances et même des utilitaires inutiles à l’exécution. Une approche multi-étapes supprime ces éléments du runtime final.

Les bénéfices typiques :

  • Taille réduite (moins de couches et d’outils inutiles)
  • Surface d’attaque diminuée (runtime minimal)
  • Meilleure conformité pour les environnements stricts (SAST/SCAP/registries)
  • Reproductibilité via des étapes et des caches maîtrisés

Architecture recommandée (builder → runtime)

Une stratégie standard consiste à :

  • Étape builder : compilation avec Maven/Gradle et récupération des dépendances
  • Étape runtime : utilisation d’un JRE/JDK minimal, ajout uniquement du binaire ou du JAR final
  • Application d’une non-root execution et d’une bonne gestion des permissions

Exemple Dockerfile multi-étapes (Maven) orienté sécurité

L’exemple ci-dessous illustre une construction efficace, avec un conteneur d’exécution non privilégié et une copie ciblée du JAR final.

Dockerfile

<!-- syntax=docker/dockerfile:1 -->
# ---------- Stage 1: Builder ----------
FROM maven:3.9-eclipse-temurin-17 AS builder
WORKDIR /build

# Réduction de la invalidation du cache : d’abord les fichiers de build
COPY pom.xml ./
COPY .mvn .mvn
# Si Gradle est utilisé, remplacer par les fichiers correspondants.

# Téléchargement des dépendances (couche cache)
RUN mvn -q -DskipTests dependency:go-offline

# Copie du code source et compilation
COPY src ./src
RUN mvn -q -DskipTests package

# ---------- Stage 2: Runtime ----------
FROM eclipse-temurin:17-jre-alpine AS runtime

# Création d'un utilisateur non-root
RUN addgroup -S appgroup && adduser -S appuser -G appgroup

WORKDIR /app

# Copie du JAR produit
# Adapter le glob au nom exact généré par le projet.
COPY --from=builder /build/target/*.jar /app/app.jar

# Permissions minimales
RUN chown -R appuser:appgroup /app
USER appuser

# Variables d'environnement (optionnel)
ENV JAVA_OPTS="-XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0"

# Port Spring Boot (optionnel selon configuration)
EXPOSE 8080

# Exécution
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar /app/app.jar"]

Sécurité : recommandations concrètes

1) Exécution en tant qu’utilisateur non-root

L’exécution en mode non-root réduit l’impact d’une compromission. Le Dockerfile de l’exemple inclut adduser/addgroup et USER appuser.

2) Runtime minimal (JRE plutôt que JDK)

Le conteneur final doit contenir uniquement ce qui est nécessaire à l’exécution. Le choix d’une image JRE (ou d’un runtime spécialisé) limite les dépendances inutiles.

3) Réduction de la surface d’attaque via le choix des bases

Les variantes Alpine ou distroless peuvent réduire la surface, mais doivent être évaluées selon la compatibilité (TLS, bibliothèques natives, besoins de glibc/musl).

4) Gestion des secrets

Les secrets ne doivent pas être intégrés au build. Ils doivent être fournis via : variables d’environnement injectées par l’orchestrateur, fichiers montés, ou solutions dédiées (Vault, KMS, etc.).

5) Distinction build vs runtime

Les outils de build (Maven/Gradle) et les caches ne sont pas copiés dans le stage final. Cela limite les risques et diminue la taille.

Taille : optimiser sans sacrifier la fiabilité

1) Utiliser uniquement le JAR final

La copie ciblée de /build/target/*.jar empêche l’inclusion de fichiers de build (sources, caches, rapports) dans l’image finale.

2) Tirer profit du cache Docker

Le découpage pom.xml → dépendances puis src → compilation améliore significativement le temps de build lors des changements mineurs.

3) Ajuster la stratégie de packaging Spring Boot

Spring Boot peut produire un artefact “fat jar” (incluant dépendances). C’est pratique, mais parfois plus volumineux. Une analyse du besoin (cold start, taille réseau, stockage registry) aide à trancher.

4) Définir des patterns de nom de JAR stables

Pour éviter des COPY fragiles, le nom du JAR peut être rendu plus prévisible. Par exemple, imposer un nom via configuration Maven, ou utiliser une expression contrôlée.

Bonnes pratiques pour Java/Spring Boot en conteneur

Contrôle de la mémoire

La JVM moderne supporte la mémoire du conteneur. L’utilisation de -XX:+UseContainerSupport et d’un MaxRAMPercentage évite des comportements incohérents entre environnements.

Options JVM via ENV

Le pattern suivant favorise la configuration externe :

ENV JAVA_OPTS="-XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0"

Exemple d’exécution avec variables

Le lancement peut être ajusté selon l’environnement, sans reconstruire l’image.

docker run --rm -p 8080:8080 \
  -e JAVA_OPTS="-XX:+UseContainerSupport -XX:MaxRAMPercentage=60.0" \
  your-registry/your-springboot-app:tag

Vérification de la conformité (taille et couches)

Pour valider les gains, il est utile d’examiner :

  • la taille de l’image (docker images)
  • le nombre de couches et leur contenu (docker history)
  • les dépendances présentes dans l’image finale (scan SCA/SBOM)

Conclusion

Une construction Docker multi-étapes pour Spring Boot améliore à la fois la sécurité (runtime minimal, non-root, surface d’attaque réduite) et la taille (copie ciblée, exclusion des outils de build). La meilleure approche combine un Dockerfile bien structuré, des choix de base adaptés et une discipline de configuration (secrets externes, réglages JVM via variables).

À propos de l'auteur

Laty Gueye Samba est développeur Full Stack basé à Dakar, Sénégal. Spécialiste des écosystèmes Java / Spring Boot et Angular.

Contact : latygueyesamba@gmail.com  |  Dakar, Sénégal

© 2026 Laty Gueye Samba.