Posted in linux, kubernetes on May 29, 2026 by Adrian Wyssmann ‐ 4 min read
My Kubernetes playground is built primarily around local
kind clusters. To make working with them more seamless, I configured custom DNS forwarding so that specific domains—such as *.cluster to automatically resolve to the respective kind cluster. This setup allows me to simulate real-world networking scenarios while keeping everything lightweight and self-contained.
kind is a tool for running local Kubernetes clusters using Docker container “nodes”. kind was primarily designed for testing Kubernetes itself, but may be used for local development or CI.
I ensure to have a dedicate interface e.g. 127.0.0.2 and bind it to hostPort 80 (and 443):
extraPortMappings:
- containerPort: 30080 # Kubernetes NodePort values must be in the range 30000–32767 by default.
hostPort: 80
listenAddress: "127.0.0.2" # Use this dedicated IP for .cluster resolution
protocol: TCPIn addition, we need to map this to a containerPort. This port reflects a nodePort, see below.
As I use dnscrypt-proxy I have to configure 2 things
Add alias for local interface
systemd.services."lo-alias" = {
description = "Add 127.0.0.2 loopback alias for kind cluster";
wantedBy = [ "network.target" ];
after = [ "network-pre.target" ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
ExecStart = pkgs.writeShellScript "lo-alias-up" ''
if ! ${pkgs.iproute2}/bin/ip addr show lo | grep -q "127.0.0.2"; then
${pkgs.iproute2}/bin/ip addr add 127.0.0.2/8 dev lo
fi
'';
ExecStop = pkgs.writeShellScript "lo-alias-down" ''
${pkgs.iproute2}/bin/ip addr del 127.0.0.2/8 dev lo 2>/dev/null || true
'';
# Ignore error if address already exists
ExecStartPre = "${pkgs.bash}/bin/bash -c '${pkgs.iproute2}/bin/ip addr show lo | grep -q 127.0.0.2 && exit 0 || true'";
};
};dnscrypt-proxy’s job is to encrypt your DNS traffic so your ISP cannot see what websites you are visiting. By default, dnscrypt-proxy sends every single request out to an encrypted public resolver on the internet. The forwaringd-rules instructs the proxy to take that specific dns request and “hand it off” to dnsmasq listening on 127.0.0.1:5353.
environment.etc = {
# Creates /etc/forwarding-rules.txt
"forwarding-rules.txt" = {
text = ''
calico 127.0.0.1:5353
cluster 127.0.0.1:5353
envoy 127.0.0.1:5353
};
}dnsmasq is lightweight dns resolver which does the internal routing. dnscrypt-proxy forwards the requests to dnsmasq. dnsmasq will then ensure requests are forwarde to the kind cluster.
services.dnsmasq = {
enable = true;
settings = {
port = 5353;
listen-address = "127.0.0.1";
bind-interfaces = true;
no-resolv = true; # don't touch upstream DNS
address = [
"/cluster/127.0.0.2"
"/calico/127.0.0.2"
"/envoy/127.0.0.2"
];
};
};Thats it.
For testing I create a new cluster with envoy proxy as ingress. I will not explain the details about the envoy setup here. The same can be achieved with another gateway as well.
Create the kind cluster mapping port host port
systemd-run --scope --user kind create cluster --name=envoy-cluster --config=envoy-cluster.yaml
kind export kubeconfig --name envoy-cluster
kubectl config use-context kind-envoy-clusterInstall envoyproxy and gateway. For that purpose I use my custom helm-chart
helm install helm-envoy-we
envoy-we ./helm-envoy-we -n envoy-we --create-namespaceinstalls envoy
installs envoy gateway – needed for the ingress
hostnames:
- "*.envoy"
- "*.cluster"
nodePort: 30080installs backend - a echo server that is listeing
image: gcr.io/k8s-staging-gateway-api/echo-basic:v20231214-v1.0.0-140-gf544a46e
hostnames:
- www.envoy
- backend.cluster
port: 3000Allow shared-gateway-access
kubectl label namespace envoy-we shared-gateway-access=trueNow everything is running. As the echo server is listening on www.cluster I can run
curl -v www.cluster
* Host www.cluster:80 was resolved.
* IPv6: (none)
* IPv4: 127.0.0.2
* Trying 127.0.0.2:80...
* Established connection to www.cluster (127.0.0.2 port 80) from 127.0.0.1 port 37880
* using HTTP/1.x
> GET / HTTP/1.1
> Host: www.cluster
> User-Agent: curl/8.19.0
> Accept: */*
>
* Request completely sent off
< HTTP/1.1 404 Not Found
< date: Fri, 29 May 2026 13:33:09 GMT
< content-length: 0
<
* Connection #0 to host www.cluster:80 left intactBy removing the need for manual port-forwarding, the setup provides a cleaner and more realistic way to access services. One limitation is that the setup currently focuses on HTTP only—TLS/HTTPS integration is not yet covered and will be explored in a follow-up blog post.