Docker pour Java : conteneuriser une application Spring Boot
Dockerfile multi-étapes, optimisation de l'image, docker-compose pour le développement local et bonnes pratiques de sécurité pour conteneuriser une application Spring Boot.
Conteneuriser une application Spring Boot semble simple au premier abord. Mais entre l’image de base correcte, le Dockerfile optimisé, la gestion de la mémoire JVM et la configuration pour les différents environnements, il y a plusieurs pièges à éviter. Ce guide couvre les bonnes pratiques.
Pourquoi Docker pour Java ?
Sans Docker, déployer une app Java nécessite d’installer la bonne version de JDK sur chaque serveur, configurer les variables d’environnement, gérer les dépendances système. Avec Docker, l’application et son environnement sont empaquetés ensemble : un seul artefact déployable partout.
Dockerfile multi-étapes
Le multi-stage build est la pratique standard pour Java. L’étape de build compile avec le JDK complet. L’image finale n’embarque que le JRE (plus léger, meilleure sécurité).
# ─────────────────────────────────────────────
# Étape 1 : Build avec Maven + JDK
# ─────────────────────────────────────────────
FROM maven:3.9-eclipse-temurin-21 AS build
WORKDIR /build
# Copier pom.xml en premier pour profiter du cache Docker
COPY pom.xml .
RUN mvn dependency:go-offline -B -q
# Copier les sources et compiler
COPY src ./src
RUN mvn package -DskipTests -B -q
# ─────────────────────────────────────────────
# Étape 2 : Image runtime minimale avec JRE
# ─────────────────────────────────────────────
FROM eclipse-temurin:21-jre-alpine AS runtime
# Utilisateur non-root (sécurité)
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
WORKDIR /app
# Copier uniquement le jar depuis l'étape build
COPY --from=build /build/target/*.jar app.jar
# Métadonnées
LABEL org.opencontainers.image.title="mon-api"
LABEL org.opencontainers.image.version="1.0.0"
# Health check
HEALTHCHECK --interval=30s --timeout=5s --start-period=60s --retries=3 \
CMD wget -qO- http://localhost:8080/actuator/health || exit 1
USER appuser
EXPOSE 8080
ENTRYPOINT ["java", \
"-XX:MaxRAMPercentage=75.0", \
"-XX:+UseContainerSupport", \
"-Djava.security.egd=file:/dev/./urandom", \
"-jar", "app.jar"]
Points clés :
-XX:UseContainerSupport : active la détection des limites mémoire du conteneur par la JVM. Sans cela, la JVM lit la RAM totale de l’hôte et peut allouer beaucoup trop.
-XX:MaxRAMPercentage=75.0 : alloue 75% de la RAM disponible dans le conteneur au heap Java.
-Djava.security.egd=file:/dev/./urandom : évite les blocages liés à l’entropie sur certains environnements Linux dans un conteneur.
Taille de l’image
# Sans multi-stage (JDK complet + sources)
$ docker images mon-api:fat
REPOSITORY TAG SIZE
mon-api fat 678MB
# Avec multi-stage (JRE Alpine)
$ docker images mon-api:slim
REPOSITORY TAG SIZE
mon-api slim 183MB
L’image Alpine réduit encore la taille, mais peut poser des problèmes avec certaines dépendances glibc. Si vous rencontrez des erreurs natives, utilisez eclipse-temurin:21-jre (Debian slim) à la place d’Alpine.
docker-compose pour le développement local
En développement, vous avez besoin de l’application + la base de données + éventuellement un broker de messages. docker-compose orchestre tout ça localement.
# docker-compose.yml
services:
app:
build: .
ports:
- "8080:8080"
environment:
SPRING_DATASOURCE_URL: jdbc:postgresql://db:5432/monapp
SPRING_DATASOURCE_USERNAME: monapp
SPRING_DATASOURCE_PASSWORD: ${DB_PASSWORD:-devpassword}
SPRING_PROFILES_ACTIVE: dev
depends_on:
db:
condition: service_healthy
volumes:
# Hot reload en dev (si vous utilisez spring-boot-devtools)
- ./target:/app/target:ro
db:
image: postgres:16-alpine
environment:
POSTGRES_DB: monapp
POSTGRES_USER: monapp
POSTGRES_PASSWORD: ${DB_PASSWORD:-devpassword}
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U monapp"]
interval: 10s
timeout: 5s
retries: 5
ports:
- "5432:5432" # Exposer pour accès depuis l'IDE
volumes:
postgres_data:
# Démarrer l'environnement complet
docker-compose up -d
# Voir les logs de l'app
docker-compose logs -f app
# Reconstruire après modification du Dockerfile
docker-compose up -d --build app
Profiles Spring par environnement
Utilisez les profils Spring pour adapter la configuration sans modifier l’image Docker.
# application-dev.yml
spring:
jpa:
show-sql: true
devtools:
restart:
enabled: true
logging:
level:
com.monapp: DEBUG
# application-prod.yml
server:
tomcat:
max-threads: 200
spring:
jpa:
show-sql: false
management:
endpoints:
web:
exposure:
include: health,info,metrics
Le profil est activé via la variable d’environnement SPRING_PROFILES_ACTIVE.
Sécurité de l’image
Utilisateur non-root : l’app ne doit jamais tourner en root dans le conteneur. Le adduser dans le Dockerfile et USER appuser l’assurent.
Pas de secrets dans l’image : ne copiez jamais .env, application-prod.yml ou des certificats dans l’image Docker. Injectez les secrets via des variables d’environnement ou des secrets Docker/Kubernetes.
Scan de vulnérabilités : intégrez Trivy ou Snyk dans votre pipeline pour scanner l’image avant déploiement.
# Scan avec Trivy
trivy image mon-api:1.0.0 --severity HIGH,CRITICAL
Mises à jour de l’image de base : surveillez les CVE sur eclipse-temurin. Reconstruisez régulièrement pour embarquer les correctifs de sécurité.
Conclusion
Un Dockerfile Spring Boot bien construit produit une image légère, sécurisée et prévisible entre les environnements. Le multi-stage build, la gestion correcte de la mémoire JVM et l’utilisateur non-root sont les trois fondamentaux à ne pas négliger. Le reste s’adapte selon les contraintes du projet.
Amine MEGDICHE
Développeur AEM & Java Full Stack — Freelance depuis 2013