Featured projectlive

The Homelab

A hybrid cloud / on-prem environment, provisioned entirely as code.

A single source of truth. Every host, subnet, and service is declared once and generates everything downstream from it: Terraform resources, GitOps manifests, DNS records, reverse-proxy config. Nothing hand-duplicated.

Architecture
EDGE Oracle Cloud - public zero-trust edge

A public-facing controller/router built on OpenZiti. Internal services are reachable with no open inbound ports on the home network.

CORE 3× on-prem Proxmox hosts

Every VM is provisioned from a single declarative host definition: no manual clicking, no drift between what's declared and what's running. Currently 21 hosts total.

RUN 3-node Talos cluster + 3× Docker host

A combined control-plane/worker Talos cluster (48 pods running) reconciled continuously by Flux GitOps, alongside Docker hosts for services that don't need an orchestrator: 11 apps on Kubernetes, 3 on Docker, 7 on their own dedicated VM.

SEC 5× security & observability tools

Wazuh SIEM, Greenbone vulnerability scanning, and an isolated attack range for hands-on offensive/defensive practice, plus LibreNMS and Nautobot for network monitoring and source-of-truth.

Full topology Every layer, from the internet down to whatever's actually running right now: not a snapshot, the same live counts and service names as the rest of this page.
Zero-trust tunnel (no open inbound ports) Physical / local network
Internet
OpenZiti overlay
Core Switch · 802.1Q trunk
Proxmox Cluster 3× physical hosts
Kubernetes 3 nodes · 48 pods
actualbudgetdocsfreecadgrafanagrocyhomepagen8nnautobotvikunjawifi_portalzabbix
Docker 3× hosts
authentiktechnitiumtraefik
Dedicated VMs 7× services
gns3greenbonehome_assistantlibrenmsmdmunifiwazuh
Network segmentation 14 VLANs off one trunk, segmented by function and trust level rather than one flat network. A compromised IoT device or the isolated attack-range lab has no path to production or management traffic by design.
CORE SWITCH · 802.1Q TRUNK
Workload tiers
DMZVLAN 10
ProductionVLAN 20
DevelopmentVLAN 30
Infra & storage
ManagementVLAN 40
Storage frontVLAN 50
Storage backVLAN 60
Isolated segments
IoTVLAN 70
Security toolsVLAN 80
Security labVLAN 90
Access tiers
GuestsVLAN 100
UsersVLAN 110
AdminsVLAN 120
VPN usersVLAN 130
Network automation Config-as-code and centralized identity, extended onto real, physical network hardware, not just VMs.

Keeping a physical switch's config honest:

Declare
Jinja template, version-controlled
Render
Golden Config renders the intended config
Commit
Output pushed back to this same repo
Compare
Diffed against the switch's live config
Visibility
Compliance report in Nautobot

The CBS350 switch's intended configuration is a real Jinja template in this repo, not a config backup nobody ever looks at again. Nautobot's Golden Config app renders it and commits the output back into this same git history, then diffs it against the switch's actual running configuration and reports drift.

How it works

Authenticating a user, one real identity for every app:

Identity
Microsoft Entra ID
Federate
Authentik OAuth2 source
Gate
Access-tier group check
Protocol
OIDC · SAML · forward auth
App
Signed in, no second password

Every app on this page traces back to the same real Microsoft Entra ID account, not a separate password per service. Authentik federates that identity in through a real OAuth2 source, then fronts every internal app as the single SSO broker: OIDC natively where an app supports it, SAML for the one that only speaks that, and a forward-auth proxy for everything else. Each app's own <service>-read/-edit/-admin Authentik groups gate whether a login reaches it at all, generated automatically the same way the rest of this repo generates things, not hand-provisioned per app. A local emergency-fallback account still exists on a handful of services, kept as a break-glass path, not the way anyone actually logs in day to day.

Reaching an app remotely, with no open inbound ports:

Client
Ziti Desktop / Mobile Edge
Overlay
OpenZiti controller + router
Proxy
Traefik · single ingress point
SSO gate
Authentik · OIDC / SAML
App
Kubernetes or Docker

Nothing on the home network ever accepts an inbound connection. A Ziti client dials out to the public controller, which brokers an outbound-only tunnel back through the home router to Traefik: the same reverse proxy every request goes through, local or remote, and it enforces an Authentik SSO gate before most apps are ever reached.

Shipping a change, GitOps-style:

Commit
git push to main
Reconcile
Flux polls the repo
Apply
Cluster converges to match
Publish
Wiki + this page's live stats regenerate

Every change to this repo, including the numbers on this page, flows through the same pipeline: commit, reconcile, apply. No manual kubectl apply and no clicking through a UI to make it real.

Enrolling a new user, without ever hand-provisioning an account:

Intake
Public form, no login required
Review
n8n checks existing identities
Approve
Slack DM, one human decision
Commit
n8n commits the new user to git
Provision
CI applies, Terraform creates the account
Enroll
Emailed a QR code to enroll their identity

The intake form is the only thing a not-yet-provisioned person can reach: they have no Ziti identity yet, so it runs over a narrowly-scoped public tunnel instead of the zero-trust overlay above. Approval isn't a rubber stamp on a database write. It's a single human decision that produces a git commit, and Terraform is still the only thing that ever actually creates the account: the same GitOps pipeline as everything else here, just triggered by a Slack click instead of by hand. A symmetric offboarding flow tears the account back down the same way.

Disaster recovery A real, dependency-ordered runbook for a total rebuild, not tribal knowledge of how it was set up last time.

Rebuilding from an empty Proxmox cluster:

Clone
git clone, secrets reconnect via Bitwarden
Bare metal
Proxmox + firewall, installed by hand
Terraform
~19 units, one dependency order
GitOps
Flux reconciles every K8s app on its own
Docs
Wiki + this page rebuild themselves

Everything in this repo except a short, explicit list of local secrets is just... in git. Cloning it and reconnecting Bitwarden Secrets Manager gets most of the way there on its own. Every VM, the Kubernetes cluster, and the app layer on top of it come back from a documented sequence of Terraform applies in real dependency order, not whatever we happened to do last time. It's honestly not a literal single command. Installing the hypervisor OS, creating VLANs on the firewall, and pushing config to two pieces of physical network hardware are genuine manual steps that haven't been scripted away yet, and the runbook calls them out explicitly instead of glossing over them. Everything downstream of that, including the Kubernetes app layer and this page's own live stats, rebuilds itself with zero further manual steps.

Real-time monitoring Zabbix, discovering what's actually running rather than a static list someone has to keep maintaining.
Coverage
Proxmox and Kubernetes via native HTTP-agent templates polling their own real APIs, everything else agentless (ICMP / TCP)
Discovery
Kubernetes node and pod conditions are LLD-discovered, not hand-declared per resource
Alerting
Slack, severity-colored, with threaded update / resolve messages
Built to catch a real outage
  • On 2026-08-08, kubelet silently stopped posting status on two Kubernetes nodes while they kept answering ICMP the whole time, invisible to a simple ping check
  • The Kubernetes template's node-condition trigger ("Is not in Ready state") exists specifically because of that incident, not as a generic best practice added up front
  • It would have caught the real failure in real time instead of after the fact
Stack
IaC
Terraform + Terragrunt, single source of truth for hosts, network, and services
GitOps
Flux CD, continuously reconciles the Kubernetes cluster from git
Zero trust
OpenZiti overlay, no open inbound ports on the home network
Identity
Authentik SSO fronting internal and Kubernetes-hosted apps
Security
Wazuh + Greenbone, plus an isolated attack range for practice
Observability
LibreNMS monitoring, Nautobot source-of-truth with Golden Config
Automation
n8n running real household/ops workflow pipelines
Backup & DR
Proxmox Backup Server + restic, with a written and tested DR runbook
How the code is organized The real layout of this repo's terraform/ directory, not a mockup.
terraform/
├── blueprints/            28 reusable modules, one per service or subsystem
│   ├── 04-docker-containers/
│   ├── 09-k8s-secrets/
│   ├── 26-nautobot-sync/
│   └── ...
├── environments/
│   ├── 01-globals/        the single source of truth
│   │   ├── hosts.hcl      every VM and physical host, declared once
│   │   ├── network.hcl    every VLAN and subnet
│   │   ├── services.hcl   every app: routing, auth, hosting tier
│   │   └── variables.hcl  shared type contracts every module validates against
│   ├── core/ · dev/ · prod/  one small terragrunt.hcl per deployed unit
│   └── root.hcl           centralized inputs, wired into every unit above
└── modules/                shared logic reused across many blueprints
Declared once, generated everywhere Adding a host to hosts.hcl is the only manual step. Everything below it regenerates on its own, with nothing hand-duplicated and nothing to keep in sync by hand.
hosts.hcl · one host entry
Provisioned
TerraformVM on Proxmox
Discoverable
DNSTechnitium record
NautobotDCIM / IPAM entry
Monitored
Zabbixhost + host group
Documented
Wikireference page regenerates
Live from the reporefreshes ~30min

Not a snapshot: pulled from the same generator that keeps the internal wiki honest. Counts and generic service names only.

Hosts managed
21 Terraform-provisioned
Kubernetes
3 nodes · 48 pods running
Services deployed
21 across the stack
Infrastructure as code
16,205 lines across 28 Terraform blueprints
Commits
2,939 to this repo
Deployed right now
actualbudgetauthentikdocsfreecadgns3grafanagreenbonegrocyhome_assistanthomepagelibrenmsmdmn8nnautobottechnitiumtraefikunifivikunjawazuhwifi_portalzabbix

Last synced: 2026-09-14T15:55:03Z

Real hardware, not just VMs
  • RADIUS/AAA live on a physical Cisco CBS350 switch
  • Nautobot + Golden Config compliance checked against real hardware
  • LibreNMS SNMP/LLDP monitoring against real gear
GNS3 labs Pulled directly from what's actually running on the lab server, not a writeup after the fact.

No labs published yet.