Skip to content

homelab-dr-blueprint

What this is, in one paragraph

A disaster-recovery and orchestration blueprint for a 4-PC home lab (Windows 11 + Docker Desktop, no always-on Linux server, churning home Wi-Fi/DHCP, one isolated subnet). It is mostly documentation — the reasoning, the decision matrix, and the migration path — plus a set of small, self-contained PowerShell scripts and Docker Compose / Kubernetes manifests that implement the recommended path (Compose-in-Git + a hardened, tested Postgres backup/promote loop) and an explicitly-labeled learning lab (k3s) for practicing Kubernetes. Nothing here assumes a datacenter: every design choice is driven by "this has to survive a DHCP lease renewal and a laptop going to sleep."

Why

A three-model council (Codex + two local models) was asked "should this homelab run Kubernetes?" and came back unanimous: no, not for production here — full k8s is overkill and fragile on Windows/WSL2 with churning addresses and local-disk-only storage. The recommended substrate is Docker Compose declared in Git, backed by a bespoke, rehearsed Postgres backup/restore-verify/promote loop. k3s is kept as a real, runnable learning lab, clearly marked not-for-production. The full reasoning is in docs/how-we-got-here.md and the scored comparison in docs/README.md.

Quickstart

# 0. Foundation first: stable node names (do this before anything else).
#    See foundation/README.md.
./foundation/Set-HomelabHostsAliases.ps1 -WhatIf

# 1. Copy env placeholders and fill them in.
cp .env.example .env

# 2. Deploy the recommended stack (Compose-in-Git).
./compose/scripts/Deploy-ComposeStack.ps1 -StackPath ./compose/stacks/claude-mem -WhatIf
./compose/scripts/Deploy-ComposeStack.ps1 -StackPath ./compose/stacks/claude-mem

# 3. Schedule the DR loop (hourly backup, nightly restore-verify).
#    See compose/README.md for the exact schtasks commands.

Every mutating script in this repo supports -WhatIf — run it dry first.

Repository layout

homelab-dr-blueprint/
├─ MIGRATION-PATH.md          # the ordered rollout: foundation -> compose -> DR -> [swarm] -> [k3s]
├─ .env.example                # placeholders only; real .env is gitignored
├─ TESTING.md                  # the 4-layer test matrix, honest about what applies to a docs-heavy blueprint
├─ docs/
│  ├─ README.md                # overview + environment-specific decision matrix
│  ├─ how-we-got-here.md       # the 3-model council journey + unanimous verdict
│  ├─ pros-cons.md             # deep pros/cons per substrate + stateful/stateless + RPO/RTO
│  ├─ when-to-graduate.md      # concrete triggers for Swarm -> k3s -> full k8s
│  ├─ MAKING-OF.md             # living journal of how this repo came together
│  ├─ BOARD.md                 # ticket-style board for work on this repo
│  ├─ technical/
│  │  ├─ ARCHITECTURE.md       # Mermaid diagrams: components + the DR sequence
│  │  └─ SECURITY.md           # secrets handling, what the scans found
│  └─ plain/
│     └─ README.md             # the plain-language sibling of this file
├─ foundation/                 # static-DHCP guidance + stable node naming (hosts/Tailscale)
├─ compose/                    # RECOMMENDED path: compose stacks + bespoke Postgres DR + optional Swarm
├─ kubernetes/                 # LEARNING path: real k3s cluster, manifests, runbook, teardown
├─ tests/                      # Pester 5+ test suite (parse, hermetic exec, markdown links, manifests)
├─ archive/                    # superseded files, never deleted (see policy below)
└─ .github/workflows/ci.yml    # test + churn + security CI

Testing

This is a docs-heavy blueprint, not an application, so the usual "4-layer" house test matrix is adapted honestly rather than forced. Short version: every PowerShell script gets Pester (parse cleanliness + hermetic -WhatIf execution where that's actually possible without Docker/elevation/real hardware), every markdown cross-link is checked by a link-checker test, and the compose/Kubernetes manifests get structural sanity checks. Full matrix and rationale in TESTING.md.

pwsh -File tests/Invoke-CI.ps1

Archive / never-delete policy

Superseded files are never deleted — they move to archive/ with a history-encoding filename (NAME.CURRENT-<date>.STALE-<date>.superseded-by-<reason>.ext) and a header explaining what replaced them and why. Archived files still have to pass the secret gate. Nothing has been superseded yet, so archive/ is currently empty except for this policy note.

What is validated vs. what needs real hardware

  • Validated here (hermetically, in CI): every PowerShell script parses cleanly; scripts that can run without Docker/elevation/WSL/SSH are actually invoked with -WhatIf under Pester; compose/Kubernetes YAML passes structural checks; every relative markdown link resolves.
  • Needs real hardware to fully exercise: Docker Compose bring-up and health gating, the Postgres backup/restore/promote loop against a live container, Swarm join across nodes, k3s server+agent join, cross-node failover timing, DHCP-reservation behavior on your specific router. These are marked in-script with # REQUIRES REAL HARDWARE banners and documented per-script in TESTING.md.

Security

  • No secrets are committed. .env is gitignored; only .env.example (placeholders) is tracked. Every script reads credentials from .env or the environment — never a literal in source.
  • A repo-wide secret-shaped-literal grep runs in CI and locally (see TESTING.md); the swarm/k3s join tokens are explicitly never logged (the k3s agent script scrubs the plaintext token from memory after use; the Swarm init script only ever prints the token it just got from Docker, which is by design ephemeral and rotatable).
  • CI runs a Semgrep scan (via Docker — no native Windows Semgrep) against the whole repo. Details and current findings in docs/technical/SECURITY.md.
  • The Windows-hosts-file and split-brain-guard scripts require Administrator to actually write anything; every mutating script supports -WhatIf so you can see exactly what would change before it does.

Where to start

  1. Read docs/how-we-got-here.md so the "why" is clear.
  2. Follow MIGRATION-PATH.md top to bottom.
  3. Do foundation/ first — nothing else is stable until addressing is.