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.
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
- Host route.
marina upadds172.30.0.0/16 → <lima0-IP>on macOS viasudo /sbin/route(this is the one-time sudo prompt). - Forwarding + SNAT exemption. Inside the VM,
ip_forward=1and a systemd oneshot apply idempotent iptables rules:- a NAT exemption (before Docker's
MASQUERADE) so kind→host traffic leaveslima0without SNAT; - DOCKER-USER forward rules that explicitly allow host→kind and established return traffic.
- a NAT exemption (before Docker's
- Survives Docker restarts. A
docker.service.ddrop-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.
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) to127.0.0.1and collide. - Kubeconfigs use the VM's
lima0IP (e.g.https://192.168.64.3:700N), L2-reachable from the host via vzNAT; the API-server cert gets thelima0IP added as a SAN automatically.
Loopback mode (disablePortMirroring: false)
- API servers listen on
0.0.0.0:700Ninside the VM. - Lima's hostagent mirrors those ports to
127.0.0.1:700Non 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)