Goal: Local Kubernetes deployment for the spring-boot-web-application-sample using Minikube.
- Minikube with a supported driver (
docker,kvm2, etc.) - kubectl
- Docker Desktop (with Rosetta enabled on Apple Silicon)
minikube start --cpus=4 --memory=8192 --driver=docker
# # or use `minikube start --driver=kvm2` if docker driver can't start
minikube addons enable ingress
minikube addons enable metrics-serverMinikube runs its own Docker daemon, separate from your laptop's Docker. Images must be
built inside it, otherwise pods will not find the docker images and will fail with ImagePullBackOff.
Run from the repo root:
# Point your shell at Minikube's Docker daemon (do this once per terminal session)
eval $(minikube docker-env)
# Generate docker images for all application images
# comment 'mvnd -T 5 clean package install' if you've already built the project
./build-docker-images.shRun all commands from the repo root.
# 1. Create the namespace
kubectl apply -f k8s/base/namespace.yaml
# 2. Apply ConfigMap and Secrets
kubectl apply -f k8s/base/config.yaml
# 3. Load Keycloak realm files as a ConfigMap
# delete existing for updates
kubectl delete configmap keycloak-realm -n sample-app-ns
kubectl create configmap keycloak-realm \
--from-file=main-app/main-webapp/src/main/resources/keycloak/ \
-n sample-app-ns
# 4. Deploy infrastructure services (database first)
kubectl apply -f k8s/base/mysql.yaml
kubectl apply -f k8s/base/activemq.yaml
kubectl apply -f k8s/base/emailhog.yaml
kubectl apply -f k8s/base/zipkin.yaml
kubectl apply -f k8s/base/keycloak.yaml
# 5. Wait for MySQL and KeyCloak to be ready before Java apps start connecting
kubectl wait --for=condition=ready pod -l app=mysql -n sample-app-ns --timeout=120s
kubectl wait --for=condition=ready pod -l app=keycloak -n sample-app-ns --timeout=120s
# Use `kubectl port-forward service/mysql 3306:3306 -n sample-app-ns` to allow host to connect to mysql running inside minikube
# 6. Deploy Java services
kubectl apply -f k8s/base/java-apps.yaml
# 7. Deploy ingress
kubectl apply -f k8s/base/ingress.yaml
# 8. Watch pods come up
kubectl get pods -n sample-app-ns -wThe Ingress routes by Host header. Both the browser and the Spring Boot pod need to resolve the hostnames to the Minikube IP.
# Get Minikube IP
minikube ip
# e.g. 192.168.49.2
# Add to /etc/hosts on your laptop
echo "192.168.39.116 app.local keycloak.local zipkin.local emailhog.local" | sudo tee -a /etc/hosts
# Verify
ping keycloak.localhostAliases: The
java-apps.yamlmain-webapp deployment includes ahostAliasesentry so the pod can resolvekeycloak.localinternally. Update the IP there to matchminikube ipif you restart Minikube.
| URL | Purpose |
|---|---|
http://app.local |
Spring Boot main webapp |
http://keycloak.local |
Keycloak Admin Console (admin / admin) |
http://zipkin.local |
Zipkin tracing UI |
http://emailhog.local |
MailHog (captured emails) |
# 1. Add the Portainer Helm repo
helm repo add portainer https://portainer.github.io/k8s/
helm repo update
# 2. Create a namespace for Portainer
kubectl create namespace portainer
# 3. Install Portainer using NodePort (easiest for Minikube)
helm install portainer portainer/portainer \
--namespace portainer \
--set service.type=NodePort \
--set service.nodePort=30777 \
--set service.httpNodePort=30778
# 4. Wait for Portainer to be ready
kubectl wait --for=condition=ready pod -l app.kubernetes.io/name=portainer \
-n portainer --timeout=120s
# 5. Get the URL and open
minikube service portainer -n portainer --url# View all deployments
kubectl get deployments -n sample-app-ns
# View all instances(pods)
kubectl get pods -n sample-app-ns
# to restart all pods from all deployments
kubectl rollout restart deployment -n sample-app-ns
# restart from a specific deployment
kubectl rollout restart deployment <deployment-name> -n sample-app-ns
# eg:
kubectl rollout restart deployment/email-service -n sample-app-ns
kubectl rollout restart deployment/report-service -n sample-app-ns
kubectl rollout restart deployment/content-checker-service -n sample-app-ns
kubectl rollout restart deployment/main-webapp -n sample-app-ns
kubectl rollout restart deployment/trend-service -n sample-app-ns
kubectl rollout restart deployment/keycloak -n sample-app-ns
kubectl rollout restart deployment/zipkin -n sample-app-ns
kubectl rollout restart deployment/emailhog -n sample-app-ns
kubectl rollout restart deployment/mysql -n sample-app-ns
# Check logs for a service
kubectl logs -f deployment/<DEPLOYMENT_NAME> -n sample-app-ns
kubectl logs deployment/content-checker-service -n sample-app-ns --tail=20
# Describe a pod (useful when a pod won't start)
kubectl describe pod <pod-name> -n sample-app-ns
# Check events (first place to look when something is wrong)
kubectl get events -n sample-app-ns --sort-by='.lastTimestamp'
# Scale a service (load balancing is automatic)
kubectl scale deployment <DEPLOYMENT_NAME> -n sample-app-ns --replicas=5
# Open the Minikube dashboard
minikube dashboard# Delete all resources in the namespace
kubectl delete namespace sample-app-ns
# Or stop Minikube entirely
minikube stopKubernetes was designed for stateless workloads. It's perfect at restarting, rescheduling, and scaling pods — but those same behaviors are dangerous for a database.
MySQL should not be deployed through kubernetes, instead it should be a 'managed DB' or 'dedicated VM'.
we are deploying MySQL from k8s for testing purpose only
Kubernetes cluster
├── main-webapp ┐
├── email-service │
├── content-checker ├─ all stateless, scale freely
├── trend-service │
├── report-service ┘
├── activemq ← acceptable in k8s with persistent volume
├── zipkin ← acceptable in k8s
└── keycloak ← acceptable in k8s (backed by external DB)
Outside Kubernetes
├── MySQL ← managed, backups, failover handled for you
└── (optional) Keycloak backed by that same DB instance