Back to Blog
Kubernetes
DevOps
Infrastructure
It Works... But It Feels Wrong - The Real Way to Run a Java Monolith on Kubernetes Without Breaking Your Brain
February 18, 2026
5 min read read
**“It Works… But It Feels Wrong” — The Real Way to Run a Java Monolith on Kubernetes Without Breaking Your Brain**
You’ve got:
- Ubuntu host
- Apache2 handling SSL
- `kind` cluster
- Moqui + OpenSearch in pods
- MySQL on the host
- Service type: NodePort
- Apache reverse proxying to `172.x.x.x:30083`
And it works.
But it feels duct-taped together.
That instinct? It’s correct.
Let’s clean this up properly — production-style, self-managed, no cloud load balancers, no magic.
---
# 1️⃣ NodePort + Apache Reverse Proxy — Is It Bad?
Short answer:
For local dev? Fine.
For production? Fragile.
Right now Apache is proxying to a `kind` internal IP .
That’s brittle because:
- `kind` is not meant for production
- Control-plane IPs are not stable design boundaries
- You’re bypassing Kubernetes’ intended ingress model
You said you don’t want cloud-managed load balancers and you’re running everything on your own machine. That’s completely fine.
The clean production-style setup on bare metal looks like this:
### Recommended Architecture (Self-Managed)
```
Internet
↓
Apache (or Nginx) — TLS termination
↓
Kubernetes Ingress Controller
↓
Service (ClusterIP)
↓
Pods
```
Instead of NodePort, install an Ingress controller like:
- NGINX (nginx-ingress)
- Traefik
Then:
- Apache proxies to the ingress controller (not NodePort)
- Or even better: remove Apache entirely and let ingress handle TLS using cert-manager
If you're staying fully self-managed on a single machine, the simplest clean version is:
- Install nginx-ingress
- Use `hostNetwork` or MetalLB if you later scale to multiple nodes
- Terminate TLS at ingress
NodePort is not “wrong.” It’s just low-level plumbing. Ingress exists so you don’t wire ports manually forever.
---
# 2️⃣ Autoscaling a Java Monolith — The Hard Truth
You’re seeing:
400–500MB RAM per pod
Scaling 1 → 3 replicas = ~1.5GB memory
Yes.
That’s how horizontal scaling works.
Kubernetes does not magically share JVM heap between replicas.
Each replica = full JVM.
That’s not a Kubernetes issue. That’s Java architecture.
But here’s where you *can* optimize.
### 🧠 JVM Tuning
If you don’t explicitly set:
```
-XX:MaxRAMPercentage
-Xms
-Xmx
```
The JVM may over-allocate.
Set memory limits in Kubernetes:
```yaml
resources:
requests:
memory: "512Mi"
limits:
memory: "512Mi"
```
Then tune JVM to respect container memory.
Otherwise your pod will use more memory than you think.
---
### ⚙ HPA Strategy
HPA works better with CPU than memory.
Memory scaling is sticky. CPU scaling reacts faster.
Your idea of 90% CPU utilization target is reasonable .
But the real optimization question is:
How many concurrent requests can one Moqui pod handle?
Measure that. That’s your scaling unit.
---
### 🚫 The Bigger Question
If you need 3 replicas for availability — fine.
If you need 3 replicas for traffic — fine.
But if you’re scaling because memory grows due to session storage?
That’s architecture smell.
Which brings us to your real pain point.
---
# 3️⃣ Sessions, Logs, Locks, and PVC Bottlenecks
You mentioned:
- `logs/`
- `txlogs/`
- `sessions/`
- Multiple replicas
- RWX PVC
- LOCK issues
This is the real blocker.
And it’s not Kubernetes’ fault.
It’s shared filesystem semantics.
Multiple Java pods writing to the same RWX volume will absolutely cause locking contention. Especially for sessions.
Here’s the clean way to think about it:
---
## 🔥 Sessions Should NOT Be On Disk
Disk-based sessions do not scale horizontally.
Proper production setup:
Use Redis.
- External Redis
- Or Redis in Kubernetes
- Or Redis cluster
Session store becomes centralized in-memory key-value store.
No file locking.
No RWX problems.
No user logout on pod switch.
This is the industry standard for Java monolith scaling.
---
## 📁 Logs Should NOT Be On Shared PVC
Logs go to stdout.
Then use:
- Fluent Bit
- Loki
- ELK stack
Kubernetes logging model expects logs on stdout, not shared files.
If you need transaction tracing, ship logs to centralized logging — not shared disk.
---
## 📦 Static Files / Uploads
Now we separate concerns:
- Sessions → Redis
- Logs → centralized logging
- Uploads → object storage
Object storage is ideal because:
- It’s replica-safe
- No locking
- No file corruption
- Scales independently
If you're fully self-hosted, run:
- MinIO (S3-compatible)
- Or NFS (but NFS is legacy compared to object storage)
RWX PVC is the least scalable option here.
---
# 4️⃣ Should MySQL Be Inside Kubernetes?
You currently run MySQL on host .
That’s actually fine.
Running databases inside Kubernetes is possible (StatefulSet), but it adds complexity:
- Volume management
- Backup orchestration
- HA complexity
If you're single-machine bare metal, keeping MySQL outside Kubernetes is completely reasonable.
If you go multi-node, reconsider.
---
# 5️⃣ Managing Kubernetes Through UI
Yes.
You can use:
- Lens
- Kubernetes Dashboard
- Portainer
Lens is by far the cleanest experience for remote cluster management.
You just download kubeconfig and connect.
No need to rely on fragile NodePort exposure.
---
# The Real Production Shape for Your Case
Since you're self-hosting on local hardware and want full control:
### Clean Architecture
- Bare metal server
- Proper Kubernetes distro (not kind)
RKE2, Talos, k3s, etc.
- nginx-ingress installed
- TLS handled at ingress
- Moqui pods (no shared filesystem for sessions)
- Redis for sessions
- Object storage (MinIO) for uploads
- MySQL either outside k8s or as StatefulSet
- Centralized logging
No NodePort.
No control-plane IP dependencies.
No RWX locks.
---
# The Hard but Honest Take
You’re not stuck because of Kubernetes.
You’re stuck because monoliths expect shared disk.
Kubernetes expects stateless pods.
Once you move:
- Sessions → Redis
- Logs → stdout
- Uploads → object storage
Scaling becomes boring.
And boring is good.
Right now your architecture works.
But it’s tightly coupled to a single-machine mental model.
Kubernetes shines when you remove filesystem coupling.
That’s the unlock.
---
Keep Exploring
Kubernetes Isn’t Your Load Balancer — It’s the Puppet Master Pulling the Strings
Kubernetes orchestrates load balancers, but does not replace them; this post explains what actually handles production traffic.
Should You Use CPU Limits in Kubernetes Production?
A grounded take on when CPU limits help, when they hurt, and how to choose based on workload behavior.
We Thought Kubernetes Would Save Us - The Production Failures No One Puts on the Conference Slides
A field report on real Kubernetes production failures and the human factors that trigger them.
Zero-Downtime Deployments Without Kubernetes: Proven Approaches
Kubernetes is not the only way to achieve zero-downtime deployments. This article covers proven alternatives such as load balancers, blue-green rollout patterns, and graceful shutdown strategies.