š Introduction
Deploying Kubernetes locally can feel like a breezeāuntil you introduce cross-OS boundaries like running MicroK8s inside an Ubuntu WSL2 instance on a Windows machine. Suddenly, networking bridges, cluster security templates, and isolated container image caches clash.
In this guide, I will take you through my entire journey from absolute scratch: initializing the cluster, bypassing the Windows-to-WSL network layer, containerizing a legacy .war file into Apache Tomcat 10, resolving local container runtime blocks, and scaling to a load-balanced multi-pod deployment.
Deployment Flow:
š ļø Phase 1: Installing and Configuring the Cluster
MicroK8s is Canonical's lightweight, fast-booting Kubernetes distribution that works natively on Ubuntu via Snap packages.
1. Installation & Init
Run the standard package manager to pull the classic snap layer down and wait for readiness flags to clear:
sudo snap install microk8s --classic
sudo microk8s status --wait-ready
sudo microk8s enable dns dashboard storage
2. The WSL2 Network Barrier š
Because the cluster builds internal authorization certificates tied exclusively to the Linux environment, a Windows web browser attempting to view the dashboard on localhost will be strictly blocked. We must patch the cluster's trusted certificate schema.
First, check your active Ubuntu network IP address:
ip route
## Look for your eth0 source IP (e.g., 172.21.236.75)
Open the root certificate mapping file:
sudo vi /var/snap/microk8s/current/certs/csr.conf.template
Scroll down to the [ alt_names ] segment and explicitly append your Ubuntu network address to the safe-list:
IP.3 = 172.21.236.75
Force a full service recycle to re-mint the secure tokens:
sudo microk8s stop && sudo microk8s start
3. Launching the Security Proxy
Extract your cluster access token:
sudo microk8s kubectl describe secret -n kube-system microk8s-dashboard-token
Spin up the connection bridge utility to forward cluster resources through to your host:
sudo microk8s dashboard-proxy
Open your Windows web browser, navigate to https://127.0.0.1:10443, bypass the browser certificate safety warnings, and paste your authorization token. You're in!
š¦ Phase 2: Containerizing Your Java Web App
Kubernetes does not manage code files directly; it orchestrates containers. We need to wrap your compiled application file into an official application server baseline.
1. Establish Your Workspace
Create a folder structure inside Ubuntu to hold your codebase assets and copy your file over:
mkdir -p /mnt/c/MyDomain/TECH/kubernetes/allwork/kubernetes-java-app
cd /mnt/c/MyDomain/TECH/kubernetes/allwork/kubernetes-java-app
2. Crafting the Dockerfile Blueprint
Create a raw text blueprint named Dockerfile to map out the Tomcat 10 installation parameters:
## Use official Tomcat 10 with Java 17 as the base foundation
FROM docker.io/tomcat:10.1-jre17
## Clean out the default placeholder webapps to prevent conflicts
RUN rm -rf /usr/local/tomcat/webapps/*
## Copy your local WAR file from your directory into Tomcat's deployment folder
COPY mywebprojectwar.war /usr/local/tomcat/webapps/
## Inform the container environment to expose Tomcat's web port
EXPOSE 8080
3. Compiling the Image Package
Because MicroK8s focuses purely on execution workloads, use buildah to safely handle compilation layers on Ubuntu:
sudo apt update && sudo apt install -y buildah
sudo buildah bud -t my-java-app:v1 .
𧬠Phase 3: The Local Sideloading Blueprint
If you try to run your containers right now, you will hit an immediate wall: ErrImageNeverPull. MicroK8s isolates local terminal compiles away from the live Kubernetes runtime container pools (k8s.io namespace).
Here is the exact side-loading injection pattern that guarantees a perfect local import:
1. Build a Standard Archive
Export the image into an unmanaged, standardized archive on your hard drive:
rm -f my-java-app.tar
sudo buildah push my-java-app:v1 docker-archive:my-java-app.tar:my-java-app:v1
2. Stream into Cluster Core Memory
Use the high-level MicroK8s packaging command along with a file stream director (<) to automatically unpack the elements straight into the cluster namespace:
sudo microk8s images import < my-java-app.tar
3. Verify Your Cache Identifiers
Query the exact internal text string that the cluster engine assigned to your package:
sudo microk8s ctr --namespace=k8s.io image list | grep managed
You will notice your file carries the string tag localhost/my-java-app:v1 bound to a specific runtime permission marker (io.cri-containerd.image=managed).
š¼ Phase 4: Defining the Orchestration Layout (YAML)
Now we write the declarative automation file. We want 2 separate copies (replicas) of Tomcat spinning up simultaneously, sitting safely behind a single load-balancing endpoint.
Create a file named app-deployment.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: java-app-deployment
labels:
app: my-java-web-app
spec:
replicas: 2
selector:
matchLabels:
app: my-java-web-app
template:
metadata:
labels:
app: my-java-web-app
spec:
containers:
- name: tomcat-container
image: localhost/my-java-app:v1
imagePullPolicy: Never # Forces K8s to look only at your hard drive cache
ports:
- containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: java-app-service
spec:
type: ClusterIP
selector:
app: my-java-web-app
ports:
- port: 8080
targetPort: 8080
Fire up the structural blueprints into the cluster engine:
sudo microk8s kubectl apply -f app-deployment.yaml
šµļø Phase 5: Verification & Stepping Inside the Matrix
Track your applications as they boot up live into active server spaces:
sudo microk8s kubectl get pods -w
When successful, your console will print:
NAME READY STATUS RESTARTS AGE
java-app-deployment-56fd4d8d5f-ddd4n 1/1 Running 0 10s
java-app-deployment-56fd4d8d5f-n69wp 1/1 Running 0 10s
Stepping Inside the Pod Environments š¤Æ
Are those Tomcats really there? Let's physically step inside the isolated filesystem of each individual running node to verify:
## Step inside Pod 1
sudo microk8s kubectl exec -it java-app-deployment-56fd4d8d5f-ddd4n -- bash
ls -la /usr/local/tomcat/webapps
exit
## Step inside Pod 2
sudo microk8s kubectl exec -it java-app-deployment-56fd4d8d5f-n69wp -- bash
ls -la /usr/local/tomcat/webapps
exit
Each container runs its own isolated filesystem partition with independent copies of Tomcat 10 and your web project!
š Phase 6: Routing Live Traffic to Windows
To view your load-balanced environment, map an open port on your host computer (like 9595) directly to your front-end Kubernetes service router:
sudo microk8s kubectl port-forward service/java-app-service 9595:8080
Keep that terminal active, switch to your Windows web browser, and access your custom project page:
http://localhost:9595/mywebprojectwar/index.jsp
Every click and refresh on that web address is now automatically round-robinned and load-balanced across your 2 independent Tomcat containers!
š§¼ Phase 7: Leaving No Trace (The Clean Slate)
When you are done experimenting and want to reclaim your system RAM, CPU cycles, and disk blocks back to a "Zero Stage" without breaking your machine, use this cleanup checklist:
## 1. Drop Applications
sudo microk8s kubectl delete -f app-deployment.yaml
sudo microk8s kubectl delete service my-app-kubernetes 2>/dev/null
sudo microk8s kubectl delete deployment my-app-kubernetes 2>/dev/null
## 2. Reclaim Image Storage Space
rm -f ~/kubernetes-java-app/*.tar
sudo buildah rmi --all
sudo microk8s ctr --namespace=k8s.io image rm $(sudo microk8s ctr --namespace=k8s.io image ls -q) 2>/dev/null
## 3. Shutdown Subsystems
sudo microk8s disable dashboard registry
sudo microk8s stop
## 4. Factory Reset (Optional)
sudo snap remove microk8s --purge && sudo snap install microk8s --classic
šÆ Conclusion
Running Kubernetes inside WSL2 is a great way to build hands-on experience without needing cloud infrastructure or multiple virtual machines. While the setup introduces a few unique challengesācertificate configuration, image management, and networkingāeach issue has a straightforward solution once you understand how the pieces fit together.
In this guide, we built a complete local Kubernetes environment from scratch, containerized a Java web application using Buildah, deployed it on MicroK8s, scaled it with multiple replicas, and exposed it through a Kubernetes Service.
More importantly, we explored why each step was necessary rather than simply copying commands. Understanding concepts such as container runtimes, image namespaces, deployments, services, and port forwarding makes it much easier to troubleshoot real Kubernetes environments.
If you're beginning your Kubernetes journey, I hope this walkthrough saves you the hours of trial and error that I went through.

Top comments (0)