Helm charts are not just for packaging; they’re the orchestrator for your Spring Boot application’s life on Kubernetes, and probes are the sentinels that ensure it stays alive and ready.

Let’s watch this Spring Boot app, my-awesome-app, deploy. We’ll use a Helm chart to manage it.

First, the Chart.yaml:

apiVersion: v2
name: my-awesome-app
version: 0.1.0
appVersion: 1.0.0
description: A Helm chart for my awesome Spring Boot app

Next, values.yaml to configure our deployment:

replicaCount: 2

image:
  repository: my-docker-repo/my-awesome-app
  tag: latest
  pullPolicy: IfNotPresent

service:
  type: ClusterIP
  port: 80

livenessProbe:
  enabled: true
  initialDelaySeconds: 15
  periodSeconds: 20
  failureThreshold: 3
  httpGet:
    path: /actuator/health/liveness
    port: 8080

readinessProbe:
  enabled: true
  initialDelaySeconds: 5
  periodSeconds: 10
  failureThreshold: 2
  httpGet:
    path: /actuator/health/readiness
    port: 8080

resources:
  limits:
    cpu: 500m
    memory: 512Mi
  requests:
    cpu: 250m
    memory: 256Mi

And finally, the core of our deployment in templates/deployment.yaml:

apiVersion: apps/v1
kind: Deployment
metadata:

  name: {{ include "my-awesome-app.fullname" . }}

  labels:

    {{- include "my-awesome-app.labels" . | nindent 4 }}

spec:

  replicas: {{ .Values.replicaCount }}

  selector:
    matchLabels:

      {{- include "my-awesome-app.selectorLabels" . | nindent 6 }}

  template:
    metadata:
      labels:

        {{- include "my-awesome-app.selectorLabels" . | nindent 8 }}

    spec:
      containers:

        - name: {{ .Chart.Name }}


          image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"


          imagePullPolicy: {{ .Values.image.pullPolicy }}

          ports:
            - name: http
              containerPort: 8080 # Spring Boot's default HTTP port
              protocol: TCP
          livenessProbe:

            {{- if .Values.livenessProbe.enabled }}


            initialDelaySeconds: {{ .Values.livenessProbe.initialDelaySeconds }}


            periodSeconds: {{ .Values.livenessProbe.periodSeconds }}


            failureThreshold: {{ .Values.livenessProbe.failureThreshold }}

            httpGet:

              path: {{ .Values.livenessProbe.httpGet.path }}


              port: {{ .Values.livenessProbe.httpGet.port }}


            {{- end }}

          readinessProbe:

            {{- if .Values.readinessProbe.enabled }}


            initialDelaySeconds: {{ .Values.readinessProbe.initialDelaySeconds }}


            periodSeconds: {{ .Values.readinessProbe.periodSeconds }}


            failureThreshold: {{ .Values.readinessProbe.failureThreshold }}

            httpGet:

              path: {{ .Values.readinessProbe.httpGet.path }}


              port: {{ .Values.readinessProbe.httpGet.port }}


            {{- end }}

          resources:

            {{- toYaml .Values.resources | nindent 12 }}

And templates/service.yaml:

apiVersion: v1
kind: Service
metadata:

  name: {{ include "my-awesome-app.fullname" . }}

  labels:

    {{- include "my-awesome-app.labels" . | nindent 4 }}

spec:

  type: {{ .Values.service.type }}

  ports:

    - port: {{ .Values.service.port }}

      targetPort: http
      protocol: TCP
      name: http
  selector:

    {{- include "my-awesome-app.selectorLabels" . | nindent 4 }}

To deploy this, you’d first package your Spring Boot app as a Docker image and push it to a registry. Then, assuming you have Helm and kubectl configured for your Kubernetes cluster:

helm install my-awesome-app ./my-awesome-app-chart

Kubernetes creates two Pods for my-awesome-app. Each Pod contains a container running your Spring Boot application. The deployment.yaml specifies livenessProbe and readinessProbe configurations.

The livenessProbe is your application’s "Are you alive?" check. If it fails repeatedly (after initialDelaySeconds and periodSeconds), Kubernetes considers the container unhealthy and restarts it. For Spring Boot, the /actuator/health/liveness endpoint, provided by the Spring Boot Actuator, is the standard. It typically returns a 200 OK if the application is running, and 500 Internal Server Error or other non-200 status codes if it’s in a bad state (e.g., database connection lost, critical component failed).

The readinessProbe is the "Are you ready to receive traffic?" check. If this probe fails, Kubernetes removes the Pod’s IP address from the Service’s endpoints, meaning no new traffic will be sent to it. This is crucial during application startup. Your Spring Boot app might be technically "alive" but still initializing its database connections or warming up caches. Once the readinessProbe starts returning 200 OK consistently, Kubernetes adds the Pod back to the Service’s endpoints. The /actuator/health/readiness endpoint is commonly used here; it’s often configured to be more stringent than liveness, checking for full operational readiness.

The initialDelaySeconds in both probes gives your application time to start up before Kubernetes begins probing. periodSeconds defines how often the probe is executed. failureThreshold is the number of consecutive failures before the probe is considered failed.

The beauty of using Helm is that these probe configurations, along with image details, replica counts, and resource requests/limits, are all managed declaratively in values.yaml. You can easily scale your application, adjust probe timings, or change resource allocations by modifying the values.yaml file and running helm upgrade my-awesome-app ./my-awesome-app-chart.

The critical piece here is that Spring Boot Actuator must be included as a dependency in your pom.xml or build.gradle to expose these /actuator/health endpoints. Otherwise, the httpGet probes will consistently fail with 404 Not Found or connection refused errors, leading to unpredictable pod behavior.

After deploying and assuming everything is configured correctly, the next thing you’ll likely encounter is managing secrets for sensitive configuration like database passwords.

Want structured learning?

Take the full Spring-boot course →