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
-WhatIfunder 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 HARDWAREbanners and documented per-script inTESTING.md.
Security¶
- No secrets are committed.
.envis gitignored; only.env.example(placeholders) is tracked. Every script reads credentials from.envor 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
-WhatIfso you can see exactly what would change before it does.
Where to start¶
- Read
docs/how-we-got-here.mdso the "why" is clear. - Follow
MIGRATION-PATH.mdtop to bottom. - Do
foundation/first — nothing else is stable until addressing is.