Home-Lab DR / Orchestration Blueprint¶
A disaster-recovery and orchestration blueprint for 4 home Windows 11 PCs, each running Docker Desktop with Linux containers. It covers two paths:
- The recommended path — static addressing + Docker Compose in Git +
hardened bespoke Postgres DR (
compose/). This is what you should actually run. - The learning path — a real, runnable k3s cluster for practicing
Kubernetes for your job (
kubernetes/). Explicitly not recommended for production in this environment, and flagged as such throughout.
TL;DR of the verdict: For this homelab, full Kubernetes is overkill and fragile. Run Compose-in-Git plus a bespoke Postgres DR you actually test. Keep the GitHub-Actions self-hosted runner out of any cluster. Use k3s as a lab to learn k8s, not as your ops substrate. The full reasoning is in
how-we-got-here.md.
The environment (constraints that drive every decision)¶
| Constraint | Consequence |
|---|---|
| 4 home Windows 11 PCs, Docker Desktop, Linux containers | Any Linux control plane runs inside WSL2/VMs — extra layers, extra failure modes. |
| No Linux servers, no always-on Linux node | Nothing is reliably "the control plane". Nodes are workstations that sleep/reboot. |
| Home Wi-Fi, churning DHCP IPs | Node addresses change under you. This breaks etcd/raft quorum and any IP-based DR reference. |
| One subnet is an isolated island | Not all nodes can reach all nodes by default — clustering assumes full mesh. |
| Local disks only — no SAN/NAS/replicated storage | No shared PersistentVolume. Stateful workloads can't just "reschedule" their data. |
| Services: claude-mem (stateful PG+Valkey+worker), a GH-Actions runner pool (already HA via GitHub), future stateless daemons | Only claude-mem is genuinely stateful and DR-critical. The runner is already orchestrated by GitHub. |
Services in scope¶
- claude-mem — stateful: Postgres + Valkey + worker. DR today = hourly
pg_dumpwarm standbys + failover scripts. This blueprint hardens that. - GitHub-Actions self-hosted runner — a warm pool of
--ephemeralrunners. GitHub is already the HA layer / scheduler. Keep it out of any cluster. One job needs a Windows host for DPAPI, so it can't move to a Linux pod anyway. - Future stateless dockerized daemons — the only workloads that would ever benefit from Swarm/k8s scheduling here, and only after the network is stable.
Decision matrix (scored against this environment)¶
Scoring: 5 = great fit here, 1 = poor fit here. These scores are environment-specific — they are not a general ranking of the technologies. Higher total = better fit for this homelab.
| Criterion (weight) | Compose-in-Git | Docker Swarm | k3s | Full k8s (kubeadm) | Bespoke DR scripts |
|---|---|---|---|---|---|
| Fit w/ Windows+Docker Desktop (x2) | 5 | 4 | 2 | 1 | 5 |
| Tolerates DHCP churn / subnet island (x3) | 5 | 2 | 2 | 1 | 4 |
| Handles STATEFUL Postgres DR (x3) | 3¹ | 1 | 2² | 2² | 5 |
| Ops burden / day-2 simplicity (x2) | 5 | 3 | 2 | 1 | 3 |
| Self-healing for STATELESS (x1) | 3³ | 4 | 5 | 5 | 1 |
| Learning value for a k8s job (x1) | 1 | 2 | 5 | 5 | 1 |
| No always-on Linux node required (x2) | 5 | 3 | 1 | 1 | 5 |
| Weighted total (max 70) | 53 | 31 | 31 | 21 | 57 |
Notes:
1. Compose alone doesn't replicate PG data — it hosts the DB; DR is the bespoke scripts' job. Scored on "doesn't fight you".
2. k3s/k8s can schedule a StatefulSet but do not replicate Postgres data; with local-disk-only storage a pod can't reschedule its data to another node. CloudNativePG helps but needs real storage + always-on nodes.
3. Compose gives per-node restart policies (restart: unless-stopped) — single-node self-healing, not cross-node rescheduling.
Read of the matrix: the recommended production substrate is
Bespoke DR scripts + Compose-in-Git (top two totals). Swarm is the only
cluster worth a later look, and only for stateless. k3s/full-k8s score low
as production here but k3s wins on learning value, which is exactly why it
lives in kubernetes/ as a lab.
Repository layout¶
homelab-dr-blueprint/
├─ README.md # top-level entry point: what/why/quickstart/security
├─ MIGRATION-PATH.md # the ordered rollout: foundation -> compose -> DR -> [swarm] -> [k3s]
├─ TESTING.md # the 4-layer test matrix, honest about substitutions
├─ .env.example # placeholders only; real .env is gitignored
├─ docs/
│ ├─ README.md # (this file) overview + decision matrix
│ ├─ how-we-got-here.md # the 3-seat 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) + SECURITY.md
│ └─ plain/ # plain-language sibling of the root README
├─ foundation/ # static-DHCP guidance + stable node naming (hosts/Tailscale)
├─ compose/ # RECOMMENDED path: compose stacks + bespoke Postgres DR + optional Swarm
│ ├─ scripts/ # deploy, backup, promote, restore-verify, primary-marker
│ ├─ stacks/ # claude-mem (PG+Valkey+worker) + sample stateless daemon
│ └─ swarm/ # optional Swarm variant (stateless only)
├─ kubernetes/ # LEARNING path: real k3s cluster, manifests, runbook, teardown
│ ├─ scripts/
│ └─ manifests/
├─ tests/ # Pester 5+ suite: parse, hermetic exec, markdown links, manifests
├─ archive/ # superseded files, never deleted (see archive/README.md)
└─ .github/workflows/ci.yml # test + churn + security CI
Where to start¶
- Read
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.
What is validated vs. what needs real hardware¶
- Validated here: all PowerShell scripts parse cleanly (AST parse via
System.Management.Automation.Language.Parser); YAML/manifest structure; doc cross-links. See each script header for its specific test status. - Needs real multi-machine hardware to fully test: Swarm join across nodes,
k3s server+agent join, cross-node failover/promote timing, CloudNativePG on
real storage, DHCP-reservation behavior on your specific router. These are
marked in-script with
# REQUIRES REAL HARDWAREbanners.