Once you’ve built a Docker container image for your Spring Boot app, the next challenge is figuring out how to write Kubernetes manifests — especially around health checks and handling sensitive configuration, which have their own K8s-specific patterns.
This article walks through the manifests for Deployment, Service, ConfigMap, and Secret step by step. For building and pushing Docker images, see Running a Spring Boot App in a Docker Container.
Prerequisites
This article assumes the following are already in place:
- Your Spring Boot 3.x app’s Docker image has been pushed to a registry
- You can access a K8s cluster via
kubectl
You will create four resources: Deployment, Service, ConfigMap, and Secret.
Preparing Your Spring Boot App
Make sure spring-boot-starter-actuator is in your dependencies so your app can respond to K8s probes.
<!-- pom.xml -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
// build.gradle
implementation 'org.springframework.boot:spring-boot-starter-actuator'
For an introduction to Actuator basics, see Getting Started with Spring Boot Actuator.
Next, enable the Kubernetes probe endpoints in application.yml:
management:
endpoint:
health:
probes:
enabled: true
endpoints:
web:
exposure:
include: health
Setting management.endpoint.health.probes.enabled=true enables /actuator/health/liveness and /actuator/health/readiness. Note that in Spring Boot 3.x, probes are automatically enabled when the KUBERNETES_SERVICE_HOST environment variable is set (i.e., when running on a K8s cluster), so whether to set this explicitly depends on your environment.
Creating the Deployment Manifest
Create a deployment.yaml file. Include both livenessProbe and readinessProbe in the same step.
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
spec:
replicas: 2
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
# imagePullSecrets: [{name: regcred}] # Add this for private registries
containers:
- name: myapp
image: your-registry/myapp:latest
ports:
- containerPort: 8080
resources:
requests:
cpu: "250m"
memory: "512Mi"
limits:
cpu: "500m"
memory: "1Gi"
envFrom:
- configMapRef:
name: myapp-config
- secretRef:
name: myapp-secret
livenessProbe:
httpGet:
path: /actuator/health/liveness
port: 8080
initialDelaySeconds: 60
periodSeconds: 10
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 8080
initialDelaySeconds: 30
periodSeconds: 5
livenessProbe checks whether the container is “alive” — if it fails, the Pod is automatically restarted. readinessProbe checks whether the container is ready to accept traffic — if it fails, the Pod is removed from the Service’s routing targets.
Set initialDelaySeconds based on your Spring Boot startup time. If it’s too short, the probe will fail before the app finishes starting, resulting in a CrashLoopBackOff. If startup takes 30 seconds, setting it to around 60 seconds provides a safe buffer. If you’re using a private registry, don’t forget to uncomment imagePullSecrets and configure credentials — otherwise you’ll get an ImagePullBackOff error.
Creating the Service Manifest
apiVersion: v1
kind: Service
metadata:
name: myapp
spec:
selector:
app: myapp
ports:
- port: 80
targetPort: 8080
type: ClusterIP
type: ClusterIP makes the service accessible only within the cluster and is the recommended default. If you want to expose the service directly to the internet in a cloud environment, change it to LoadBalancer and a cloud provider load balancer will be provisioned automatically.
Injecting Environment-Specific Properties with ConfigMap
Non-sensitive configuration values should be managed via ConfigMap.
apiVersion: v1
kind: ConfigMap
metadata:
name: myapp-config
data:
SPRING_PROFILES_ACTIVE: "production"
APP_EXTERNAL_API_URL: "https://api.example.com"
Passing SPRING_PROFILES_ACTIVE as an environment variable causes application-production.yml to be loaded automatically. For more on profiles and external properties, see the Spring Boot Properties Configuration Guide and Using Spring Boot Profiles to Switch Environment-Specific Configuration Safely.
Passing Database Credentials with Secret
Sensitive values such as passwords should be managed via Secret.
apiVersion: v1
kind: Secret
metadata:
name: myapp-secret
type: Opaque
stringData:
SPRING_DATASOURCE_URL: "jdbc:postgresql://db:5432/mydb"
SPRING_DATASOURCE_USERNAME: "appuser"
SPRING_DATASOURCE_PASSWORD: "your-secret-password"
Using stringData allows you to write values in plain text. If you use the data field instead, values must be Base64-encoded.
One important caveat: Kubernetes Secrets are only Base64-encoded by default — they are not encrypted. Since they are stored as-is in etcd, consider enabling etcd encryption or adopting an external secret management tool such as External Secrets Operator for production environments. The rule of thumb for choosing between ConfigMap and Secret is simple: use ConfigMap for settings that are safe to expose, and Secret for passwords and API keys.
Applying and Verifying with kubectl apply
# Apply
kubectl apply -f configmap.yaml
kubectl apply -f secret.yaml
kubectl apply -f deployment.yaml
kubectl apply -f service.yaml
# Check status
kubectl get pods
kubectl describe pod <pod-name>
kubectl logs <pod-name>
# Verify access from local machine
kubectl port-forward svc/myapp 8080:80
If kubectl get pods shows a STATUS of Running and READY of 2/2 (when replicas is 2), the deployment is successful.
After updating a ConfigMap or Secret, a Pod restart is required. Environment variables loaded via envFrom are only read at startup, so run kubectl rollout restart deployment/myapp to apply the changes.
Summary
Key points for deploying a Spring Boot app to Kubernetes:
- Set
management.endpoint.health.probes.enabled=true(may be auto-enabled in K8s environments viaKUBERNETES_SERVICE_HOST) - Wire
/actuator/health/livenesstolivenessProbeand/actuator/health/readinesstoreadinessProbe - Set
initialDelaySecondswith enough buffer beyond your app’s startup time - Manage non-sensitive config in ConfigMap and passwords in Secret
- Secrets are only Base64-encoded, not encrypted — additional safeguards are needed in production
- Pod restarts are required after updating ConfigMap or Secret
Integration with Helm charts and CI/CD pipelines will be covered in a future article.