# thesabear's Homelab
Most homelabs are a media server with some extra apps bolted on. This one is a platform: the same categories of systems a company's platform/security team owns — secrets management, zero-trust network segmentation, self-hosted CI/CD, human-gated fleet orchestration, a self-built SOC — run at homelab scale, for real, 24/7, by one person.
11 fleet hosts |
55 compose stacks |
16 documented systems |
4 trust boundaries |
13 automated maintenance flows |
## The decision that shows the most
Most self-hosted setups have one trust boundary: "you're on the LAN, you're trusted." That's the easy answer, and it's wrong — a single compromised container can reach everything else with no further resistance. The other easy answer, mutual TLS on every single connection with no exceptions, is also wrong: real overhead, real certificate-lifecycle burden, on traffic that doesn't need it — exactly the kind of complexity that gets quietly disabled under pressure, which is worse than never building it.
This fleet runs **four trust boundaries, each calibrated to who's actually on the other end** — public internet traffic, human browsers on personal devices, machine-provisioned containers, and non-HTTP infra traffic each get a different control, matched to their real risk, not a blanket policy applied everywhere or nowhere. Enforced by **three independent routing authorities** instead of one central router, specifically so a failure or misconfiguration in any single one only ever takes out its own third of the traffic — never all of it at once. It didn't start this way, either: the public proxy used to be its own VM, and the internal service-to-service model used to be a flat, unauthenticated mesh with no boundary at all. Both got replaced deliberately, trading operational simplicity for a smaller blast radius.
Full breakdown — the boundary-by-boundary reasoning, the routing-authority failure table, all 8 traffic laws: [docs/architecture-overview.md](docs/architecture-overview.md).
This repo is a sanitized showcase of the systems behind that platform — the actual engineering decisions and tradeoffs, not just a list of installed apps. Every real IP, hostname, and internal domain has been stripped or genericized throughout.
## Architecture at a glance
```mermaid
flowchart LR
Internet(["Internet"]) --> Edge["Edge Firewall"]
Edge --> LAN["LAN"]
LAN --> DNS["Internal DNS
two zones"]
LAN --> Vault["Vault
PKI + secrets"]
DNS --> Vault
Vault --> Apps["App Host
41 stacks"]
Vault --> Automation["Automation VM
Git + orchestration"]
LAN --> FileServer["File Server"]
DNS --> FileServer
LAN --> Automation
Apps --> InternalProxy["Internal Proxy Zone"]
Apps --> SSO["SSO"]
Vault --> EdgeProxy["User-Facing Proxy
mTLS"]
DNS --> EdgeProxy
Apps -.proxied.-> EdgeProxy
SSO -.auth.-> EdgeProxy
Apps -.-> Monitoring["Monitoring"]
Automation -.-> Monitoring
FileServer -.-> Monitoring
EdgeProxy -.-> Monitoring
Apps -.-> SecMon["Security Monitoring"]
Automation -.-> SecMon
FileServer -.-> SecMon
EdgeProxy -.-> SecMon
classDef edge fill:#f8cecc,stroke:#b85450,color:#333
classDef core fill:#dae8fc,stroke:#6c8ebf,color:#333
classDef obs fill:#fff2cc,stroke:#d6b656,color:#333
class Internet,Edge edge
class LAN,DNS,Vault,Apps,Automation,FileServer,InternalProxy,SSO,EdgeProxy core
class Monitoring,SecMon obs
```
Solid arrows are real dependencies; dotted arrows are asynchronous telemetry reporting, not a blocking dependency. Full breakdown of all 8 traffic laws (ingress, east-west, secrets, DNS, observability, notifications, CI/CD, management) with the actual trust-boundary reasoning: [docs/architecture-overview.md](docs/architecture-overview.md).
## Every system, at a glance
| System | The core decision | Doc |
|---|---|---|
| 🔐 Secrets & PKI | One Vault cluster as CA + secret store, zero plaintext secrets in git | [→](docs/secrets-and-pki.md) |
| 🔑 Secret Distribution | Four distinct flows, each with its own failure contract | [→](docs/secret-distribution.md) |
| 🪪 SSO & Identity | Dual-zone SSO registration, header-based fallback for a real LDAP limitation | [→](docs/sso-and-identity.md) |
| 🧱 Firewall & Network | Zero blanket-allow rules — every flow explicit, everything else dropped | [→](docs/firewall-and-network.md) |
| 🌐 DNS | Two zones decide which trust boundary a request enters | [→](docs/dns-naming.md) |
| 📈 Observability | Push-based metrics, chosen specifically to fit the firewall model | [→](docs/observability.md) |
| 🛡️ Security Monitoring | Curated alert runbook + justified compliance exceptions | [→](docs/security-monitoring.md) |
| 🔁 Orchestration | 13 flows, check → pause → human approval → apply | [→](docs/orchestration.md) |
| 🔔 Notifications | Two fan-in branches, one shared formatter, zero runtime coupling | [→](docs/notifications.md) |
| 📦 Registry & Build | Self-hosted registry, digest-checked before any rebuild | [→](docs/registry-and-build.md) |
| 🖥️ Dashy SOC Board | 14 data sources aggregated into one glanceable board | [→](docs/dashy-soc-board.md) |
| ⏱️ Cron Scheduler | Fleet-wide job inventory, weighted load heatmap | [→](docs/cron-scheduler.md) |
| 📝 Content Pipeline | Content tracker → automation → blog; social fan-out still incomplete | [→](docs/content-pipeline.md) |
| 🏗️ Infrastructure as Code | Packer → Terraform → Ansible, one tag threading all three | [→](docs/iac-pipeline.md) |
| 🧯 Disaster Recovery | 3 independent recovery layers, each covering a different kind of loss | [→](docs/disaster-recovery.md) |
| 📐 Architecture Overview | 4 trust boundaries, 3 routing authorities, calibrated not maxed | [→](docs/architecture-overview.md) |
## Systems
### 🔐 Secrets & PKI
Every secret in this fleet used to sit in plaintext inside git-tracked `.env` files — readable by anyone with repo access, permanent in git history even after being "fixed." Today the real value of any secret lives in exactly one place, rendered locally to disk by an agent that authenticates itself, and nothing else ever calls the vault directly. Even the platform's own biggest risk — a bootstrap token that never expires — is ranked #1 of 3 known gaps, not left unranked.
→ [docs/secrets-and-pki.md](docs/secrets-and-pki.md)
### 🔑 Secret Distribution Model
A certificate expiring is a slow problem measured in days. A missing application secret is often an outage measured in seconds. Treating those two the same was the mistake avoided here: four separate delivery contracts instead of one universal pipeline, each built around how badly its own specific failure actually hurts.
→ [docs/secret-distribution.md](docs/secret-distribution.md)
### 🪪 SSO & Identity
The identity provider's own LDAP layer flatly refuses to authenticate superuser accounts — a hard vendor limitation, not a bug to file a ticket against. The fix wasn't a workaround bolted onto LDAP; it was routing that one case through a completely different authentication path instead, one zone boundary away from where the app itself actually lives.
→ [docs/sso-and-identity.md](docs/sso-and-identity.md)
### 🧱 Firewall & Network Segmentation
Zero blanket internal trust — every real service-to-service flow earns its own explicit rule, everything else gets logged and dropped. Even that model has a named limit worth knowing: container traffic slips past the host firewall entirely, so the real protection on any given host is narrower than the ruleset alone would suggest.
→ [docs/firewall-and-network.md](docs/firewall-and-network.md)
### 🌐 DNS
Two internal zones exist for one reason: which zone a hostname resolves in decides whether a request gets treated as a human on a browser or a machine calling another machine. Get that resolution wrong and the wrong trust boundary applies to that request — not a naming detail, the actual security-relevant part of the whole model.
→ [docs/dns-naming.md](docs/dns-naming.md)
### 📈 Observability
Metrics get pushed outward, never pulled — a call made specifically so the monitoring stack never needs inbound access punched through the firewall model above to every single host it watches. The collection agent runs natively on every host, not in a container, installed by a role that keeps re-checking itself rather than trusting it stayed correct after day one.
→ [docs/observability.md](docs/observability.md)
### 🛡️ Security Monitoring
Routine alerts get root-caused exactly once and never re-investigated from scratch again — including one case where the alert itself turned out to be the bug, not the system it was checking. Compliance hardening applies fleet-wide with named, reasoned exceptions instead of blanket enforcement, and the benchmarking tool runs as a native binary after its own official container image hit a real compatibility wall.
→ [docs/security-monitoring.md](docs/security-monitoring.md)
### 🔁 Orchestration
Nothing changes on this fleet automatically. Every update starts as a dry run, reports exactly what it would do, and then sits paused — sometimes for days — until a human actually approves it. Thirteen flows run this way, and one genuinely hard edge case (the automation engine updating the very node it runs on) gets handled with a fire-and-forget path built specifically for that one scenario.
→ [docs/orchestration.md](docs/orchestration.md)
### 🔔 Notifications
Every alert from every source — apps, hosts, security events — comes out formatted identically, because all of it funnels through one shared step before reaching Slack. That includes the debt: one decommissioned app's routing code is still sitting in the flow unswept, and a credential that should live in exactly one place is actually kept in sync by hand across three.
→ [docs/notifications.md](docs/notifications.md)
### 📦 Self-Hosted Registry & Build Pipeline
Runs on the Git server's own built-in registry — no separate service to operate, no runtime dependency on Docker Hub. A cheap digest check runs before every possible rebuild, so a week where nothing upstream actually changed costs almost nothing to verify.
→ [docs/registry-and-build.md](docs/registry-and-build.md)
### 🖥️ Dashy SOC Board
Fourteen different systems — alerts, logs, metrics, CI health, secrets state, DNS, backups — used to mean fourteen different tabs to check. One custom-built dashboard joins all of it into a single board, with acknowledgement state that lives entirely in the browser: nothing to host, nothing to secure, nothing to back up.
→ [docs/dashy-soc-board.md](docs/dashy-soc-board.md)
### ⏱️ Cron Scheduler
Every scheduled job across the fleet lives in one inventory, rendered as a load heatmap weighted by actual resource impact — a backup job counts for more than a lightweight health check, because an overlapping backup is a worse collision than two quiet jobs sharing an hour.
→ [docs/cron-scheduler.md](docs/cron-scheduler.md)
### 📝 Content Pipeline
The one system in this repo that isn't finished — included anyway, because a portfolio of only-finished systems doesn't show how anything actually gets built. A content tracker, an automation glue layer, and a blog already work end to end; the social-publishing leg is still being wired up.
→ [docs/content-pipeline.md](docs/content-pipeline.md)
### 🏗️ Infrastructure as Code
Image builds, provisioning, and configuration stay as three separate tools with three separate rhythms — an image gets rebuilt rarely, a VM gets provisioned once, config gets re-applied continuously — connected by one deliberate handoff instead of one tool trying to do all three jobs at once.
→ [docs/iac-pipeline.md](docs/iac-pipeline.md)
### 🧯 Disaster Recovery & Upgrades
"A lost host" and "a database's lost data" are different kinds of loss needing different answers, so three independent recovery layers each cover one — with one real gap between them: a storage pool covered by neither. OS and application upgrades stay deliberately decoupled too: batching them together would let one host's expected no-op silently abort every other host's run.
→ [docs/disaster-recovery.md](docs/disaster-recovery.md)
## What's running
~55 Docker Compose stacks, grouped by purpose rather than by which host happens to run them — see [`stacks/`](stacks/): identity & security, platform/infra, media, productivity, and dev tools.
---
## About me
Self-taught, building toward a career in IT/DevOps — everything in this repo is something I learned by doing, from reading docs to breaking my own lab and fixing it again.
[](https://www.linkedin.com/in/michael-surgey)
[](https://nomadianation.thesabear.net/)