This is a living document, and is subject to change at any given time.
Last updated August 5th, 2026.
DNS is layered rather than flat: Pi-hole for ad blocking up front, CoreDNS behind it as the actual client-facing resolver, and Knot behind that holding the authoritative zone data. Pi-hole runs as a primary and a replica kept in sync via Nebula-sync, while CoreDNS and Knot each sit behind their own keepalived VRRP pair. The full design, including the deliberate core.vninja.com/apps.vninja.com zone split and how Caddy fits in, is covered in How DNS and Caddy Work in My Homelab. The day-to-day mechanics of adding or removing a host or a whole zone are covered in Managing Hosts and Zones in My Homelab’s DNS.
DNS Resolution#
flowchart TD
clients["LAN clients"]
subgraph pihole["Pi-hole pair"]
direction LR
ph1["pihole-1 (primary)"]
ph2["pihole-2 (replica)"]
end
subgraph corend["CoreDNS + Knot"]
direction LR
cvip["coredns-vip"]
kvip["knot-vip"]
end
pub["Public resolvers"]
clients -->|"ad-blocked path"| ph1
clients -->|"ad-blocked path"| ph2
clients -->|"unfiltered, by design"| cvip
ph1 -->|"non-blocked queries"| cvip
ph2 -->|"non-blocked queries"| cvip
ph1 -.->|"Nebula-sync"| ph2
cvip -->|"core.vninja.com / apps.vninja.com"| kvip
cvip -.->|"everything else"| pub
classDef external fill:#d97706,fill-opacity:0.18,stroke:#d97706,stroke-width:1px
class pub external
Reaching Things From Outside#
Internal services stay off port-forwards entirely. Anything that needs to be reachable from outside goes through a Cloudflare Tunnel with Cloudflare Access in front of it, and anything genuinely public runs behind a second, fully separate DMZ Caddy with its own VLAN, host, and Cloudflare token.
flowchart LR
lanuser["LAN user"]
extuser["External user"]
subgraph cf["Cloudflare"]
edge["Cloudflare edge"]
access["Cloudflare Access
one-time code gate"]
end
caddy["Caddy, ctr-core-1
wildcard TLS via DNS-01
*.core.vninja.com + *.apps.vninja.com"]
internal["Internal services
core + apps zones"]
cfd["cloudflared
ansible/cloudflare"]
tunneled["Internal services
published via tunnel"]
dmzcaddy["DMZ Caddy, ctr-dmz-1
own VLAN, own host, own Cloudflare token"]
public["Public-facing services"]
lanuser -->|"https://*.core / *.apps"| caddy --> internal
extuser --> edge --> access -->|"tunnel, no port-forward"| cfd --> tunneled
extuser --> dmzcaddy --> public
classDef external fill:#d97706,fill-opacity:0.18,stroke:#d97706,stroke-width:1px
class edge,access external