Spring Boot

Déployer une application Spring Boot sur Kubernetes : guide complet

Guide pas-à-pas pour déployer Spring Boot sur Kubernetes : Dockerfile optimisé, manifests K8s, health checks, ConfigMaps et bonnes pratiques.

Jean-Michel Helem

Jean-Michel Helem

18 février 2026 · 6 min de lecture

Déployer une application Spring Boot sur Kubernetes : guide complet

Vous avez une application Spring Boot qui tourne parfaitement en local. Maintenant, il faut la déployer sur Kubernetes. Ce guide vous accompagne de A à Z, du Dockerfile optimisé jusqu'au déploiement production-ready.

Prérequis

  • Une application Spring Boot fonctionnelle
  • Docker installé
  • Accès à un cluster Kubernetes (minikube, kind, ou cloud)
  • kubectl configuré

Étape 1 : Préparer l'application Spring Boot

Configuration des health checks

Kubernetes a besoin de savoir si votre application est vivante et prête. Spring Boot Actuator fournit ces endpoints automatiquement.



    org.springframework.boot
    spring-boot-starter-actuator
# application.yaml
management:
  endpoints:
    web:
      exposure:
        include: health,info,prometheus
  endpoint:
    health:
      probes:
        enabled: true
      show-details: when_authorized
  health:
    livenessstate:
      enabled: true
    readinessstate:
      enabled: true

server:
  shutdown: graceful

spring:
  lifecycle:
    timeout-per-shutdown-phase: 30s

Cette configuration expose :

  • /actuator/health/liveness : l'application est-elle vivante ?
  • /actuator/health/readiness : l'application peut-elle recevoir du trafic ?

Externaliser la configuration

Ne hardcodez jamais les valeurs de configuration. Utilisez des variables d'environnement.

# application.yaml
spring:
  datasource:
    url: jdbc:postgresql://${DB_HOST:localhost}:${DB_PORT:5432}/${DB_NAME:myapp}
    username: ${DB_USERNAME:postgres}
    password: ${DB_PASSWORD:postgres}

app:
  feature:
    new-ui: ${FEATURE_NEW_UI:false}
  api:
    external-url: ${EXTERNAL_API_URL:http://localhost:8081}

Étape 2 : Créer un Dockerfile optimisé

Dockerfile multi-stage

Un Dockerfile optimisé réduit la taille de l'image et accélère les déploiements.

# Dockerfile
# Stage 1 : Build
FROM eclipse-temurin:21-jdk-alpine AS builder

WORKDIR /app

# Copier les fichiers de dépendances d'abord (cache Docker)
COPY pom.xml mvnw ./
COPY .mvn .mvn

# Télécharger les dépendances (couche cachée)
RUN ./mvnw dependency:go-offline -B

# Copier le code source
COPY src src

# Build l'application
RUN ./mvnw package -DskipTests -B

# Extraire les layers Spring Boot (optimisation)
RUN java -Djarmode=layertools -jar target/*.jar extract

# Stage 2 : Runtime
FROM eclipse-temurin:21-jre-alpine

# Sécurité : ne pas exécuter en root
RUN addgroup -S spring && adduser -S spring -G spring
USER spring:spring

WORKDIR /app

# Copier les layers dans l'ordre de fréquence de changement
COPY --from=builder /app/dependencies/ ./
COPY --from=builder /app/spring-boot-loader/ ./
COPY --from=builder /app/snapshot-dependencies/ ./
COPY --from=builder /app/application/ ./

# Port exposé (documentation)
EXPOSE 8080

# Health check Docker natif
HEALTHCHECK --interval=30s --timeout=3s --start-period=60s --retries=3 \
    CMD wget -qO- http://localhost:8080/actuator/health/liveness || exit 1

# Démarrage avec optimisations JVM pour containers
ENTRYPOINT ["java", \
    "-XX:+UseContainerSupport", \
    "-XX:MaxRAMPercentage=75.0", \
    "-Djava.security.egd=file:/dev/./urandom", \
    "org.springframework.boot.loader.launch.JarLauncher"]

Pourquoi cette structure ?

OptimisationBénéfice
Multi-stageImage finale ~200MB au lieu de ~500MB
Layers Spring BootRebuild rapide (seule la couche application change)
JRE AlpineImage légère et sécurisée
User non-rootSécurité renforcée
UseContainerSupportJVM respecte les limites CPU/RAM du container

Build et test local

# Build l'image
docker build -t mon-api:1.0.0 .

# Test local
docker run -p 8080:8080 \
    -e DB_HOST=host.docker.internal \
    -e DB_PASSWORD=secret \
    mon-api:1.0.0

# Vérifier les health checks
curl http://localhost:8080/actuator/health

Étape 3 : Créer les manifests Kubernetes

Structure recommandée

k8s/
├── namespace.yaml
├── configmap.yaml
├── secret.yaml
├── deployment.yaml
├── service.yaml
└── ingress.yaml (optionnel)

Namespace

# k8s/namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: mon-api
  labels:
    app: mon-api
    environment: production

ConfigMap

# k8s/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: mon-api-config
  namespace: mon-api
data:
  DB_HOST: "postgres.database.svc.cluster.local"
  DB_PORT: "5432"
  DB_NAME: "myapp"
  FEATURE_NEW_UI: "true"
  EXTERNAL_API_URL: "http://external-api.external.svc.cluster.local"
  JAVA_OPTS: "-XX:MaxRAMPercentage=75.0 -XX:+UseG1GC"

Secret

# k8s/secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: mon-api-secrets
  namespace: mon-api
type: Opaque
data:
  DB_USERNAME: cG9zdGdyZXM=        # echo -n 'postgres' | base64
  DB_PASSWORD: c3VwZXJzZWNyZXQ=    # echo -n 'supersecret' | base64

Important : Ne committez jamais les Secrets en clair. Utilisez :

  • Sealed Secrets
  • External Secrets Operator
  • Vault

Deployment

# k8s/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: mon-api
  namespace: mon-api
  labels:
    app: mon-api
spec:
  replicas: 3
  selector:
    matchLabels:
      app: mon-api
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
  template:
    metadata:
      labels:
        app: mon-api
      annotations:
        prometheus.io/scrape: "true"
        prometheus.io/port: "8080"
        prometheus.io/path: "/actuator/prometheus"
    spec:
      serviceAccountName: mon-api
      securityContext:
        runAsNonRoot: true
        runAsUser: 1000
        fsGroup: 1000
      containers:
        - name: api
          image: mon-registry.io/mon-api:1.0.0
          imagePullPolicy: Always
          ports:
            - name: http
              containerPort: 8080
              protocol: TCP
          envFrom:
            - configMapRef:
                name: mon-api-config
            - secretRef:
                name: mon-api-secrets
          resources:
            requests:
              memory: "512Mi"
              cpu: "250m"
            limits:
              memory: "1Gi"
              cpu: "1000m"
          livenessProbe:
            httpGet:
              path: /actuator/health/liveness
              port: 8080
            initialDelaySeconds: 60
            periodSeconds: 10
            timeoutSeconds: 5
            failureThreshold: 3
          readinessProbe:
            httpGet:
              path: /actuator/health/readiness
              port: 8080
            initialDelaySeconds: 30
            periodSeconds: 5
            timeoutSeconds: 3
            failureThreshold: 3
          lifecycle:
            preStop:
              exec:
                command: ["/bin/sh", "-c", "sleep 10"]
      terminationGracePeriodSeconds: 45
      affinity:
        podAntiAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
            - weight: 100
              podAffinityTerm:
                labelSelector:
                  matchLabels:
                    app: mon-api
                topologyKey: kubernetes.io/hostname

Explication des paramètres critiques

Probes :

ParamètreValeurExplication
initialDelaySeconds60Spring Boot démarre lentement, attendre
periodSeconds10Fréquence de vérification
failureThreshold3Nombre d'échecs avant action

Resources :

resources:
  requests:    # Minimum garanti
    memory: "512Mi"
    cpu: "250m"
  limits:      # Maximum autorisé
    memory: "1Gi"
    cpu: "1000m"
  • requests : utilisé pour le scheduling (où placer le Pod)
  • limits : protection contre les fuites mémoire

Lifecycle preStop :

lifecycle:
  preStop:
    exec:
      command: ["/bin/sh", "-c", "sleep 10"]

Laisse le temps au load balancer de retirer le Pod avant l'arrêt.

Service

# k8s/service.yaml
apiVersion: v1
kind: Service
metadata:
  name: mon-api
  namespace: mon-api
  labels:
    app: mon-api
spec:
  type: ClusterIP
  ports:
    - port: 80
      targetPort: 8080
      protocol: TCP
      name: http
  selector:
    app: mon-api

ServiceAccount (sécurité)

# k8s/serviceaccount.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: mon-api
  namespace: mon-api
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: mon-api-role
  namespace: mon-api
rules: []  # Aucune permission par défaut
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: mon-api-rolebinding
  namespace: mon-api
subjects:
  - kind: ServiceAccount
    name: mon-api
roleRef:
  kind: Role
  name: mon-api-role
  apiGroup: rbac.authorization.k8s.io

Étape 4 : Déployer

Ordre de déploiement

# Créer le namespace
kubectl apply -f k8s/namespace.yaml

# Créer les configurations
kubectl apply -f k8s/configmap.yaml
kubectl apply -f k8s/secret.yaml
kubectl apply -f k8s/serviceaccount.yaml

# Déployer l'application
kubectl apply -f k8s/deployment.yaml
kubectl apply -f k8s/service.yaml

# Vérifier le déploiement
kubectl -n mon-api get pods -w

Vérifications

# État des pods
kubectl -n mon-api get pods

# Logs
kubectl -n mon-api logs -l app=mon-api -f

# Décrire un pod (debugging)
kubectl -n mon-api describe pod mon-api-xxxxx

# Test de connectivité interne
kubectl -n mon-api run curl --rm -it --image=curlimages/curl -- \
    curl http://mon-api/actuator/health

Étape 5 : Mise à jour de l'application

Déploiement d'une nouvelle version

# Option 1 : Modifier le Deployment
kubectl -n mon-api set image deployment/mon-api \
    api=mon-registry.io/mon-api:1.1.0

# Option 2 : Modifier le fichier et apply
kubectl apply -f k8s/deployment.yaml

# Suivre le rollout
kubectl -n mon-api rollout status deployment/mon-api

Rollback si problème

# Voir l'historique
kubectl -n mon-api rollout history deployment/mon-api

# Rollback à la version précédente
kubectl -n mon-api rollout undo deployment/mon-api

# Rollback à une version spécifique
kubectl -n mon-api rollout undo deployment/mon-api --to-revision=2

Bonnes pratiques production

1. Resource Quotas

Limitez les ressources par namespace :

apiVersion: v1
kind: ResourceQuota
metadata:
  name: mon-api-quota
  namespace: mon-api
spec:
  hard:
    requests.cpu: "4"
    requests.memory: 8Gi
    limits.cpu: "8"
    limits.memory: 16Gi
    pods: "20"

2. Pod Disruption Budget

Garantissez une disponibilité minimale :

apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: mon-api-pdb
  namespace: mon-api
spec:
  minAvailable: 2
  selector:
    matchLabels:
      app: mon-api

3. Horizontal Pod Autoscaler

Scalez automatiquement selon la charge :

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: mon-api-hpa
  namespace: mon-api
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: mon-api
  minReplicas: 3
  maxReplicas: 10
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 70
    - type: Resource
      resource:
        name: memory
        target:
          type: Utilization
          averageUtilization: 80

Checklist déploiement

Avant de déployer en production :

  • [ ] Health checks configurés (liveness + readiness)
  • [ ] Graceful shutdown activé
  • [ ] Dockerfile multi-stage optimisé
  • [ ] User non-root dans le container
  • [ ] Resources (requests/limits) définies
  • [ ] Secrets non committés
  • [ ] Pod anti-affinity configuré
  • [ ] PodDisruptionBudget défini
  • [ ] HPA configuré si nécessaire
  • [ ] Logs structurés (JSON)
  • [ ] Métriques Prometheus exposées

Debugging des problèmes courants

Le Pod ne démarre pas

# Vérifier les événements
kubectl -n mon-api describe pod mon-api-xxxxx

# Erreurs courantes :
# - ImagePullBackOff : vérifier le nom de l'image et les credentials
# - CrashLoopBackOff : voir les logs avec --previous
# - Pending : vérifier les resources disponibles

L'application ne répond pas

# Vérifier les probes
kubectl -n mon-api get pods -o wide

# Port-forward pour test local
kubectl -n mon-api port-forward pod/mon-api-xxxxx 8080:8080

# Test direct
curl http://localhost:8080/actuator/health

Problèmes de performance

# Vérifier l'utilisation des resources
kubectl -n mon-api top pods

# Vérifier les limites
kubectl -n mon-api describe pod mon-api-xxxxx | grep -A 5 "Limits:"

Conclusion

Déployer Spring Boot sur Kubernetes demande de la préparation : health checks, Dockerfile optimisé, manifests bien configurés. Mais une fois en place, vous bénéficiez de la scalabilité, de la résilience et de l'automatisation de Kubernetes.

Les points clés à retenir :

  • Toujours configurer liveness et readiness probes
  • Utiliser un Dockerfile multi-stage avec les layers Spring Boot
  • Définir les resources requests et limits
  • Externaliser toute la configuration
  • Prévoir le graceful shutdown

---

Pour les fondamentaux Kubernetes : Kubernetes pour développeurs : ce qu'il faut vraiment maîtriser

Pour les breaking changes Spring Boot 4 : Spring Boot 4 : breaking changes à connaître

Articles similaires

Migrer vers Java 25 avec Spring Boot : checklist et pièges à éviter
Java

Migrer vers Java 25 avec Spring Boot : checklist et pièges à éviter

Java 25 LTS est sorti depuis septembre 2025, Spring Boot 3.4 le supporte officiellement depuis décembre 2025 — et pourtant, en mars 2026, la majorité des applications Spring Boot en production tournent encore sur Java 17 ou 21. Les raisons sont compréhensibles : les migrations LTS sont perçues comme risquées, chronophages et peu prioritaires quand l'application fonctionne. Ce guide propose une approche structurée pour démystifier la migration, en couvrant les incompatibilités réelles, les gains

Jean-Michel Helem · 9 mars 2026 · 4 min
Java 25 LTS : le guide complet des nouveautés pour les développeurs
Java

Java 25 LTS : le guide complet des nouveautés pour les développeurs

Java 25 est devenu GA (General Availability) le 16 septembre 2025, marquant la première version LTS (Long-Term Support) depuis Java 21 (sorti en septembre 2023). Oracle s'engage sur un support de 8 ans minimum, ce qui en fait la cible de migration évidente pour toutes les équipes encore sur Java 17 ou 21. En mars 2026, l'adoption s'accélère : les principaux frameworks (Spring Boot, Quarkus, Micronaut) supportent pleinement Java 25, et les cloud providers proposent des runtimes stables. Ce gui

Jean-Michel Helem · 4 mars 2026 · 5 min
Déboguer une Application Spring Boot en Production : Outils et Méthodologie
Debug

Déboguer une Application Spring Boot en Production : Outils et Méthodologie

Guide expert débogage Spring Boot production 2026 : thread dumps, heap dumps, profiling, distributed tracing. Méthodologie complète + outils.

Jean-Michel Helem · 10 février 2026 · 8 min