Documentation menu

Networking & L3 routing

Most local-Kubernetes tools hide cluster services behind port-forwards or NAT. klimax does the opposite: it routes the kind bridge subnet from your Mac into the VM at layer 3, so a pod IP is the address you connect to — and the pod sees your Mac's real source IP back.

Your Mac curl VIP macOS route 172.30/16 → lima0 lima0 vzNAT ip_forward=1 kind bridge br-xxxx 172.30.0.1 Pod / VIP MetalLB 172.30.1.200 REQUEST → dst 172.30.1.200 reply — src 172.30.1.200, dst = your Mac (no SNAT, source IP preserved)
A request and its reply. No port-mapping, no address translation rewriting the source.

The network stack

macOS host
  bridge1xx (host IP, macOS-assigned)
      │  vzNAT — Apple VZNATNetworkDeviceAttachmentLima VZ guest
  lima0 (guest IP, macOS-assigned)
  br-xxxx (172.30.0.1/16)   ← Docker bridge "kind"
      │
  kind nodes (172.30.N.x)

vzNAT IPs are assigned by macOS and can't be configured. klimax detects the VM's lima0 IP at runtime — nothing is hardcoded — so multiple Lima VMs each get a distinct IP on their own bridge and coexist without conflict.

How pure L3 routing works

  1. Host route. klimax up adds 172.30.0.0/16 → <lima0-IP> on macOS via sudo /sbin/route (this is the one-time sudo prompt).
  2. Forwarding + SNAT exemption. Inside the VM, ip_forward=1 and a systemd oneshot apply idempotent iptables rules:
    • a NAT exemption (before Docker's MASQUERADE) so kind→host traffic leaves lima0 without SNAT;
    • DOCKER-USER forward rules that explicitly allow host→kind and established return traffic.
  3. Survives Docker restarts. A docker.service.d drop-in re-runs the rules whenever Docker restarts.

The result: curl http://172.30.1.200/ on your Mac reaches the MetalLB VIP directly, and the pod sees your real source address.

Reaching the API server: two modes

klimax supports two ways for kubeconfigs to address cluster API servers, controlled by network.disablePortMirroring.

Direct IP — disablePortMirroring: true · default kubectl on your Mac lima0 VM NIC · 192.168.64.3 kube-apiserver :700N · cert SAN += lima0 DIRECT L3 NO LIMA PORT MIRROR ~/.kube/config → https://192.168.64.3:700N Loopback — disablePortMirroring: false kubectl on your Mac Lima port mirror host + guest agent · gRPC kube-apiserver 0.0.0.0:700N in VM 127.0.0.1:700N MIRRORS LISTEN SOCKETS ~/.kube/config → https://127.0.0.1:700N
Same kubectl, two address paths. The default (direct L3 to lima0) coexists with other Lima VMs; set disablePortMirroring: false to fall back to stable 127.0.0.1 loopback.

Direct-IP mode (default — disablePortMirroring: true)

  • Lima's TCP port mirroring is off, so klimax coexists with other Lima-based VMs (Colima, Rancher Desktop) that also manage kind clusters — otherwise both VMs race to mirror the same port (e.g. 7001) to 127.0.0.1 and collide.
  • Kubeconfigs use the VM's lima0 IP (e.g. https://192.168.64.3:700N), L2-reachable from the host via vzNAT; the API-server cert gets the lima0 IP added as a SAN automatically.

Loopback mode (disablePortMirroring: false)

  • API servers listen on 0.0.0.0:700N inside the VM.
  • Lima's hostagent mirrors those ports to 127.0.0.1:700N on the host.
  • Kubeconfigs point at https://127.0.0.1:700N — a stable address that survives VM restarts. Use it when running a single klimax VM, or when host security software (e.g. CrowdStrike) blocks direct vzNAT IPs.

disablePortMirroring is a VM-level setting — it only takes effect on a fresh VM (klimax destroy && klimax up). With the default, the lima0 IP is assigned dynamically and may change on restart; re-run klimax kubeconfig merge <name> afterwards to refresh ~/.kube/config.

Registry traffic

Every cluster's containerd is patched to transparently cache pulls from docker.io, quay.io, gcr.io, and Google Artifact Registry (us-docker.pkg.dev, us-central1-docker.pkg.dev) through the pull-through mirrors. All mirror containers join the kind Docker network, so nodes resolve them by hostname. See Registry mirrors.

🔍

klimax status shows the VM state, the host route, and whether the iptables rules are present — start there when connectivity looks off, then klimax doctor.

The route line names the gateway it resolved, so you can confirm it points at the VM's lima0 address rather than being answered by some broader route:

$ klimax status
=== macOS Route ===
  172.30.0.0/16 → present (via 192.168.64.3)