Building a Local Kubernetes Development Machine with Lima-vm
What? #
A development machine is a tool that developers can use to get feedback on what they’re working on. The key is that this machine runs locally on a laptop/computer, provided it has adequate resources.
Having a development machine available locally is expected to increase the number of iterations and feedback received more frequently.
By receiving frequent feedback, developers can perform several iterations called the Inner loop independently. Once confident enough, the work can be passed on to other team members for other iterations such as testing, bug fixing, etc., which is called the Outer loop. Here’s an illustration taken from the Sourcegraph blog.

Why? #
As a developer who daily interacts with applications deployed on Kubernetes, it’s good to get closer to this technology.
This is one of my reasons for creating an isolated “Development Box” in the form of a virtual machine. It contains several related tools for learning Kubernetes.
Previously I used Multipass.run and Microk8s, but this time let’s try exploring Kind with Lima-vm.
How? #
Lima-vm installation can follow my previous post here. Besides that, there are several other tools that need to be installed.
Creating a VM with Docker Template #
limactl start template://dockerAdd the --rosetta flag if using Apple silicon chip and want to run x86-based containers.
Installing CLI Tools #
Install the following CLI tools - these should already be installed inside the Lima-vm docker. Here’s how to install them:
Creating a Kind Cluster with Ctlptl #
First, make sure you’re inside the Lima-vm docker
limactl shell dockerCreate a kind.yaml file
---
apiVersion: ctlptl.dev/v1alpha1
kind: Registry
name: ctlptl-registry
port: 5005
---
apiVersion: ctlptl.dev/v1alpha1
kind: Cluster
product: kind
registry: ctlptl-registry
kindV1Alpha4Cluster:
name: my-cluster
nodes:
- role: control-plane
- role: workerWhy use Ctlptl? Because ctlptl can create clusters based on config files, which will be useful later if you want to share configs with other team members.
The cluster config above already includes a local registry if we want to build local images and push them to a local registry that can be pulled by the Kind cluster. Some examples of ctlptl definitions can be seen in this repo.
However, for a more comfortable iteration of the above workflow, I suggest running it together with Tilt.dev, which will be discussed in the next article.
Create the cluster with the following command.
jimbo@lima-docker:~/hello-podinfo$ ctlptl apply -f kind.yaml
Creating registry "ctlptl-registry"...
registry.ctlptl.dev/ctlptl-registry created
No kind clusters found.
Creating cluster "my-cluster" ...
✓ Ensuring node image (kindest/node:v1.32.2) 🖼
✓ Preparing nodes 📦 📦
✓ Writing configuration 📜
✓ Starting control-plane 🕹️
✓ Installing CNI 🔌
✓ Installing StorageClass 💾
✓ Joining worker nodes 🚜
Set kubectl context to "kind-my-cluster"
You can now use your cluster with:
kubectl cluster-info --context kind-my-cluster
Thanks for using kind! 😊
Connecting kind to registry ctlptl-registry
Switched to context "kind-my-cluster".
🔌 Connected cluster kind-my-cluster to registry ctlptl-registry at localhost:5005
👐 Push images to the cluster like 'docker push localhost:5005/alpine'
cluster.ctlptl.dev/kind-my-cluster createdMake sure you’re connected to the Kind cluster
jimbo@lima-docker:~/hello-podinfo$ kubectl cluster-info
Kubernetes control plane is running at https://127.0.0.1:34881
CoreDNS is running at https://127.0.0.1:34881/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy
To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.From the Ctlptl cluster definition in the kind.yaml file above, we defined a cluster consisting of 1 server and 1 worker. We can verify this with the kubectl get nodes command
jimbo@lima-docker:~/hello-podinfo$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
my-cluster-control-plane Ready control-plane 40m v1.32.2
my-cluster-worker Ready <none> 40m v1.32.2Deploy Demoapp to the Kind Cluster #
Create a demoapp.yaml file with the following content.
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: demoapp
spec:
replicas: 1
selector:
matchLabels:
app: demoapp
template:
metadata:
labels:
app: demoapp
spec:
containers:
- name: demoapp
image: docker.io/hashicorp/demo-webapp-lb-guide
ports:
- containerPort: 9898
env:
- name: PORT
value: "9898"
- name: HOST_IP
valueFrom:
fieldRef:
fieldPath: status.podIPExecute the deployment with kubectl
jimbo@lima-docker:~/hello-podinfo$ kubectl apply -f demoapp.yaml
deployment.apps/demoapp createdWait until the deployment is ready
jimbo@lima-docker:~/hello-podinfo$ kubectl get deployments --watch
NAME READY UP-TO-DATE AVAILABLE AGE
demoapp 0/1 1 0 35s
demoapp 1/1 1 1 45sGet the pod name you want to test, you can use the following command.
jimbo@lima-docker:~/hello-podinfo$ kubectl get pods
NAME READY STATUS RESTARTS AGE
demoapp-6c66595c9f-bnjsw 1/1 Running 0 6m14sDo a port-forward to access the pod.
jimbo@lima-docker:~/hello-podinfo$ kubectl port-forward pod/demoapp-6c66595c9f-bnjsw 9090:9898
Forwarding from 127.0.0.1:9090 -> 9898
Forwarding from [::1]:9090 -> 9898Try sending a request to the pod with curl, you can even request from the host machine because Lima-vm already supports automatic port forwarding.
jimbo@host:~$ curl http://localhost:9090
Welcome! You are on node 10.244.1.4:9898Conclusion #
My habit of using WSL in Windows can also be replicated in the MacOS/Linux environment with Lima-vm. We can do development/experiment iterations more freely without worrying about accidentally disturbing the main host/machine we use daily because it’s already isolated in a virtual machine.
Thank you to all the creators and maintainers of the tools above, my quality of life has improved!