NodeZero is a public engineering handbook for a self-hosted homelab, rebuilt end-to-end in v2.0 on Ubuntu Server LTS. It documents a single always-on LAN server providing DNS filtering with DNSSEC-validating recursive resolution, an internal TLS reverse proxy, file sharing, media services, ebook hosting, and media automation.
v2.0 is an as-built record: everything described here was actually deployed, run, and verified on the machine (37→40 automated checks in scripts/90-verify.sh). Where something is deliberately not done, the handbook says so and why.
| Field | Value |
|---|---|
| Current Version | NodeZero v2.0 |
| Status | Deployed and verified |
| Operating System | Ubuntu Server LTS (SSD) |
| Storage | 1 TB Toshiba HDD (ext4, USB 3.0, /mnt/hdd) |
| Network | LAN-only, static reservation 192.168.0.77 (D-Link DIR-825 DHCP reservation by MAC) |
| Application Model | Docker Compose |
| License | MIT |
v1.0 documented a Debian 13 design with Tailscale remote access, UFW firewalling, and a planned backup story. v2.0 is the result of an explicit simplification decision:
- Removed Tailscale / all VPN / remote access. The server is LAN-only by decision. Smaller attack surface, fewer moving parts.
- Removed UFW rules. Redundant on a trusted LAN behind a NAT router; one less failure domain (stale nftables rules caused real outages during the rebuild).
- Removed backups by decision. The operator accepts data-loss risk for media; configs are reconstructible from this repo. This is a conscious, documented trade-off, not an oversight.
- Moved to Ubuntu Server LTS (from Debian 13) on the SSD.
- Added internal TLS: Dockerized Caddy terminates TLS for
*.nodezerowith a self-hosted root CA installed on client devices. - DNSSEC-validated recursive DNS: Pi-hole forwards to Unbound (recursive resolver, DNSSEC validation on); verified with
dnssec-failed.org→ SERVFAIL. - HDD reformatted exFAT → ext4. exFAT has no Linux permission model; Docker bind mounts require it.
- SSH hardening: key-only by default with optional password, plus a FIDO2/U2F YubiKey-backed
ed25519-skkey as an alternative factor (key lives on the YubiKey, generated client-side on macOS).
flowchart TD
Clients["LAN clients (Mac, iOS, TVs)"]
Router["D-Link DIR-825 (DHCP + reservation)"]
Pihole["Pi-hole :53 (filtering, local records)"]
Unbound["Unbound :5335 (recursive, DNSSEC)"]
Caddy["Caddy :80/:443 (internal TLS, *.nodezero)"]
Docker["Docker Compose services"]
HDD["HDD ext4 /mnt/hdd (appdata, media)"]
SSD["SSD: Ubuntu Server LTS (OS only)"]
Clients --> Router --> Pihole --> Unbound
Clients --> Caddy --> Docker --> HDD
SSD --- HDD
Diagram source: assets/diagrams/nodezero-architecture.mmd
| Service | URL (LAN) | Role |
|---|---|---|
| Homarr | https://home.nodezero |
Dashboard / homepage |
| Jellyfin | https://jellyfin.nodezero |
Media server |
| Kavita | https://books.nodezero |
Ebook / comic server |
| Sonarr | https://sonarr.nodezero |
TV automation |
| Radarr | https://radarr.nodezero |
Movie automation |
| Prowlarr | https://prowlarr.nodezero |
Indexer manager |
| Bazarr | https://bazarr.nodezero |
Subtitles |
| qBittorrent | https://qb.nodezero |
Torrent client |
| Pi-hole | https://dns.nodezero/admin/ |
DNS filtering (web UI on :8083) |
| Dawarich | https://location.nodezero |
Location history |
| Traccar | https://traccar.nodezero |
GPS tracking (host port 8082) |
| SearXNG | https://search.nodezero |
Metasearch |
| Samba | \\192.168.0.77 |
File sharing |
LAN client → Pi-hole (:53, filtering + local *.nodezero records)
→ Unbound (:5335, recursive from root, DNSSEC validation)
→ authoritative servers
Verification: cloudflare.com +dnssec returns NOERROR with ad; dnssec-failed.org returns SERVFAIL.
Everything is scripted and idempotent — safe to re-run:
tar -xzf nodezero-rebuild-kit.tar.gz && cd nodezero
nano config.env # set HOST_IP, passwords (never committed)
sudo FORMAT_DISK=1 ./rebuild.sh # formats HDD to ext4, installs everything
sudo ./scripts/99-finish.sh # DNS records + dashboard + ARR wiring + verification99-finish.sh is the single post-install entry point. It runs, in order:
| Script | Purpose |
|---|---|
93-dns-records.sh |
Writes Pi-hole local A records for every *.nodezero host and reloads FTL |
91-homarr.sh |
Seeds the Homarr board with all service tiles |
92-arr-config.sh |
Wires Sonarr/Radarr/Prowlarr/Bazarr/qBittorrent through their APIs |
90-verify.sh |
40-point health check (services, DNS chain, DNSSEC, TLS, storage, ports) |
Client-side DNS is the only step that runs off the server: sudo bash scripts/mac-dns.sh
on macOS, or set the router's DHCP DNS server to the host IP with DNS relay disabled.
Caddy's admin API on :2019 is intentionally not published to the host — it can
rewrite the entire reverse-proxy configuration without authentication. The verification
script therefore asserts both that the API answers inside the container and that it is
unreachable from the host.
See docs/README.md for the chapter-by-chapter handbook, SECURITY.md for the threat model and honest known weaknesses, and CHANGELOG.md for what v2.0 changed and why.
- The operating system is disposable; persistent data lives on dedicated storage.
- Applications are containerized with clear service boundaries.
- LAN-only: no VPN, no port forwarding, no public exposure.
- Automation over manual UI configuration; idempotent scripts over click-ops.
- Public documentation contains no secrets — placeholders only.