klimax is now marina. Same tool, new name since v0.3. Existing install? Migrate with marina migrate →
Documentation menu

Networking & L3 routing

Most local-Kubernetes tools hide cluster services behind port-forwards or NAT. marina 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 VZNATNetworkDeviceAttachment
      ▼
Lima 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. marina 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. marina 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

marina 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 marina 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 marina VM, or when host security software (e.g. CrowdStrike) blocks direct vzNAT IPs.
⚠

To change disablePortMirroring on an existing VM, edit the config and run marina up: it offers a VM restart to apply it. A cluster keeps the API address it was created with, so recreate clusters to switch them (going back to direct mode breaks loopback-mode clusters: their certificates have no lima0 SAN). With the default, the lima0 IP is assigned dynamically and may change on restart; re-run marina kubeconfig merge <name> afterwards to refresh ~/.kube/config.

Local DNS names

The same route carries DNS. Every LoadBalancer Service resolves as <service>.<namespace>.<cluster>.demo.internal: the Mac's /etc/resolver/demo.internal points at a CoreDNS container on the kind network, so lookups travel the host route like any other packet — and keep working when the lima0 address changes. See Local DNS names.

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.

🔍

marina status shows the VM state, the host route, and whether the iptables rules are present — start there when connectivity looks off, then marina 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:

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