Skip to main content
  1. pages/

Home Lab: DNS Resolution

·320 words·2 mins·
Table of Contents
Home Lab - This article is part of a series.
Part 4: This Article
Note

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
Home Lab - This article is part of a series.
Part 4: This Article

Related