DEV Community

Hamdi (KHELIL) LION
Hamdi (KHELIL) LION

Posted on

Building a Kubernetes HomeLab - The Hard Way - DNS first, or everything else lies to you πŸ”₯🌐

Let’s be very clear from the start πŸ‘‡
If DNS is shaky, everything above it becomes unreliable, misleading, and borderline hostile.

You will blame Kubernetes.
You will blame cert-manager.
You will blame yourself.

And it will still be DNS πŸ˜…

This article builds a real DNS layer, outside the cluster, the way it is done in production environments.
Not fancy. Not clever. Just solid.

  1. DNS is not a feature, it is the foundation 🧱

Most homelabs treat DNS like a checkbox:

  • router default
  • maybe 1.1.1.1
  • maybe 8.8.8.8
  • call it a day

This works until you introduce:

  • multiple Proxmox nodes
  • Kubernetes bootstrap
  • internal TLS
  • ExternalDNS
  • service to service communication At that point, DNS stops being optional. It becomes infrastructure.

If this layer lies, everything else lies with it.

  1. what I assume before we start breaking things πŸ”§

This article assumes:

  • you are comfortable with Linux and SSH
  • you control your LAN
  • you can assign static IPs
  • you are fine rebuilding things if needed

Network baseline used in examples:

  • LAN CIDR: 192.168.1.0/24
  • router: 192.168.1.1
  • DNS node: 192.168.1.169
  • internal domain: home.arpa

Why home.arpa?

  • defined by RFC 8375
  • designed for private networks
  • no collision with public DNS
  • predictable behavior across operating systems

.local is convenient.
.local is also a trap ☠️

  1. how DNS flows in this homelab, no magic involved 🧭

The DNS request path is intentionally boring:

client
β†’ Pi-hole
β†’ Unbound
β†’ root servers
β†’ TLD servers
β†’ authoritative servers

Why this design?

Pi-hole sits in front:

  • single DNS endpoint for the entire LAN
  • full visibility into queries
  • local DNS records for infrastructure
  • API compatible with ExternalDNS later

Unbound sits behind:

  • full recursive resolver
  • DNSSEC validation
  • zero dependency on Google or Cloudflare
  • behaves like enterprise DNS stacks

One DNS server for the LAN:

  • deterministic behavior
  • easier troubleshooting
  • required for Kubernetes stability
  1. building the DNS node, properly and calmly 🧱 A Raspberry Pi that does exactly one thing πŸ₯§

Reserve a static IP from your router:

  • hostname: dns-01
  • ip: 192.168.1.169

Update the system:

sudo apt update
sudo apt upgrade -y
sudo apt install curl gnupg lsb-release -y
Enter fullscreen mode Exit fullscreen mode

This node is sacred:

  • DNS only
  • no Docker
  • no Kubernetes
  • no experiments

If DNS breaks, you want zero doubt about the cause.

  1. installing Pi-hole, the DNS entry point 🚦

Pi-hole will be the only DNS server exposed to the LAN.

Install it:

curl -sSL https://install.pi-hole.net | bash

Enter fullscreen mode Exit fullscreen mode

During the installer:

  • interface: eth0
  • upstream DNS: custom
  • do not select any public DNS provider
  • enable the web admin interface

Once installed:

http://192.168.1.169/admin

Enter fullscreen mode Exit fullscreen mode

Immediately change the admin password:

pihole -a -p
Enter fullscreen mode Exit fullscreen mode

At this stage:

  • Pi-hole is answering DNS queries
  • but it should not resolve anything yet
  1. installing Unbound, the actual DNS resolver πŸ”

Unbound will do the real work: recursive resolution and DNSSEC validation.

Install Unbound:

sudo apt install unbound -y
Enter fullscreen mode Exit fullscreen mode

Create a dedicated configuration for Pi-hole:

sudo tee /etc/unbound/unbound.conf.d/pi-hole.conf > /dev/null <<EOF
server:
  interface: 127.0.0.1
  port: 5335
  do-ip4: yes
  do-ip6: no
  do-udp: yes
  do-tcp: yes
  root-hints: "/var/lib/unbound/root.hints"
  harden-glue: yes
  harden-dnssec-stripped: yes
  use-caps-for-id: yes
  edns-buffer-size: 1232
  prefetch: yes
  verbosity: 1

forward-zone:
  name: "."
  forward-addr: 0.0.0.0
EOF
Enter fullscreen mode Exit fullscreen mode

Fetch root hints:

sudo curl -o /var/lib/unbound/root.hints https://www.internic.net/domain/named.root

Enable and start Unbound:

sudo systemctl restart unbound
sudo systemctl enable unbound

At this point:

  • Unbound listens on 127.0.0.1:5335
  • performs full recursive resolution
  • validates DNSSEC
  1. connecting Pi-hole to Unbound, the clean way πŸ”—

In the Pi-hole admin UI:

  • settings
  • dns
  • upstream DNS servers
  • custom server: 127.0.0.1#5335
  • disable all other upstream resolvers

Now the chain is complete:

  • clients talk to Pi-hole
  • Pi-hole forwards to Unbound
  • Unbound talks to the internet

No leaks.
No shortcuts.

  1. naming things like an adult 🏷️

internal DNS records

Define local DNS records in Pi-hole:

proxmox-01.home.arpa   192.168.1.41
proxmox-02.home.arpa   192.168.1.42
proxmox-03.home.arpa   192.168.1.43
nas-01.home.arpa       192.168.1.50
Enter fullscreen mode Exit fullscreen mode

ip plan

component   | ip
router          | 192.168.1.1
dns-01          | 192.168.1.169
proxmox-01  | 192.168.1.41
proxmox-02  | 192.168.1.42
proxmox-03  | 192.168.1.43
synology    | 192.168.1.50
Enter fullscreen mode Exit fullscreen mode

Predictable naming beats clever naming.
Every single time.

  1. proof before trust, always πŸ”

Test recursive resolution:

dig google.com @192.168.1.169

Enter fullscreen mode Exit fullscreen mode

Test DNSSEC:

dig dnssec-failed.org @192.168.1.169

Enter fullscreen mode Exit fullscreen mode

Expected result:

  • SERVFAIL means DNSSEC is working βœ… Test internal names:
dig proxmox-01.home.arpa
Enter fullscreen mode Exit fullscreen mode

Watch live DNS traffic:

pihole -t
Enter fullscreen mode Exit fullscreen mode

If something breaks later, this is where you start.

  1. how people accidentally sabotage their own DNS ☠️

Adding a public DNS as secondary:

  • internal names stop resolving
  • ExternalDNS becomes unreliable
  • cert-manager fails silently

Using .local:

  • mDNS conflicts
  • inconsistent resolution
  • debugging hell

Running DNS inside Kubernetes first:

  • bootstrap deadlock
  • cluster depends on itself
  • guaranteed pain
  1. why this DNS layer unlocks everything else πŸš€

With this setup in place:

  • Proxmox nodes resolve consistently
  • Kubernetes nodes bootstrap cleanly
  • ExternalDNS can manage records safely
  • cert-manager can issue internal certificates
  • ingress becomes predictable

This is the point where the homelab stops feeling fragile.

Next article:
Teaching your router who’s boss πŸ§ πŸ“‘

  1. lessons learned the hard way, so you do not have to πŸ˜…

DNS should be boring.
Silent.
Predictable.

If DNS is exciting, something is wrong.

This setup is:

  • reproducible
  • observable
  • production-inspired
  • intentionally boring

Hard now.
Peace later.

Happy Clustering :)

Top comments (0)