---
title: "Building a Local Kubernetes Development Machine with Lima-vm"
description: "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."
publishedAt: 2025-04-12
locale: en
urlSlug: membangun-local-kubernetes-development-dengan-lima-vm
isDraft: false
defaultLocale: en
---
[TOC]

## 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](https://sourcegraph.com/blog/developer-productivity-thoughts) blog.

![Inner loop and Outer loop](https://cdn.prod.website-files.com/6750d0c3f154999a486dade7/67a9b6e6f0fe04e8eab1bf23_Image%202-10-25%20at%201.20%E2%80%AFAM.avif)

## 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](belajar-virtual-machine-multipass) and Microk8s, but this time let's try exploring [Kind](https://github.com/kubernetes-sigs/kind) with [Lima-vm](https://github.com/lima-vm/lima).

## How?

Lima-vm installation can follow my previous post [here](install-warp-client-fedora-desktop). Besides that, there are several other tools that need to be installed.

### Creating a VM with Docker Template

```bash
limactl start template://docker
```

Add 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:

1. [Kubectl](https://kubernetes.io/docs/tasks/tools/install-kubectl-linux/)
2. [Kind](https://kind.sigs.k8s.io/docs/user/quick-start/#installation)
3. [Ctlptl](https://github.com/tilt-dev/ctlptl?tab=readme-ov-file#how-do-i-install-it)

### Creating a Kind Cluster with Ctlptl

First, make sure you're inside the Lima-vm docker

```bash
limactl shell docker
```

Create a kind.yaml file

```yaml
---
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: worker
```

Why use [Ctlptl](https://github.com/tilt-dev/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](https://github.com/tilt-dev/ctlptl/tree/main/examples) repo.

However, for a more comfortable iteration of the above workflow, I suggest running it together with [Tilt.dev](https://tilt.dev), which will be discussed in the next article.

Create the cluster with the following command.

```bash
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 created
```

Make sure you're connected to the Kind cluster

```bash
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

```bash
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.2
```

### Deploy Demoapp to the Kind Cluster

Create a `demoapp.yaml` file with the following content.

```yaml
---
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.podIP
```

Execute the deployment with kubectl

```bash
jimbo@lima-docker:~/hello-podinfo$ kubectl apply -f demoapp.yaml
deployment.apps/demoapp created
```

Wait until the deployment is ready

```bash
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           45s
```

Get the pod name you want to test, you can use the following command.

```bash
jimbo@lima-docker:~/hello-podinfo$ kubectl get pods
NAME                       READY   STATUS    RESTARTS   AGE
demoapp-6c66595c9f-bnjsw   1/1     Running   0          6m14s
```

Do a port-forward to access the pod.

```bash
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 -> 9898
```

Try sending a request to the pod with `curl`, you can even request from the host machine because Lima-vm already supports automatic port forwarding.

```bash
jimbo@host:~$ curl http://localhost:9090
Welcome! You are on node 10.244.1.4:9898
```

## Conclusion

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!
