Kubernetes on Flatcar Container Linux + kubeadm · TrueNAS NVMe-oF storage (scale-csi) · GitOps managed
This repository contains the configuration for my homelab Kubernetes cluster built for learning, experimentation, and running self-hosted applications. The setup emphasizes Infrastructure as Code (IaC) and GitOps practices using Flatcar Container Linux + kubeadm, Kubernetes, Flux, Renovate, and GitHub Actions.
Architecture: The cluster runs on Proxmox VE 9.2 with scale-csi providing primary storage — NVMe-oF/TCP (plus iSCSI and NFS) volumes served by a TrueNAS SCALE box over its WebSocket API — and node-local scratch volumes via the OpenEBS hostpath provisioner backed by a dedicated ZFS RAID10 SSD pool on the hypervisor.
Built from scratch — ~25,000 lines of Go powering the entire cluster lifecycle.
homeops-cli is a purpose-built command-line tool that automates every operational aspect of this infrastructure. It is not a wrapper around shell scripts — it's a full Go application with 19 internal packages, native API clients, and embedded template rendering.
The built binary and CLI command name are homeops-cli.
| Command | What it does |
|---|---|
homeops-cli bootstrap --provider flatcar |
End-to-end Flatcar/kubeadm cluster init: kubeadm init/join over SSH, then Cilium + CRDs + Flux, with preflight checks and 1Password secret injection |
homeops-cli flatcar deploy-vm |
Provisions Flatcar Container Linux VMs on Proxmox (Ignition via fw_cfg) with batch/concurrent deployment |
homeops-cli flatcar render-ignition |
Renders the Butane→Ignition config for a node (debug/inspection) |
homeops-cli k8s view-secret |
Decodes secret data with interactive secret and namespace selection |
homeops-cli volsync snapshot |
Triggers Kopia-backed PVC snapshots via VolSync |
homeops-cli volsync restore |
Point-in-time PVC recovery from Kopia repository |
homeops-cli volsync migrate |
Safe storage-class migration of an app's PVC (fresh backup → guarded cutover → restore-on-create) |
homeops-cli workstation |
Developer workstation setup and validation |
Key internals:
- Native API clients for Proxmox VE and TrueNAS Scale, plus Flatcar release-image resolution — no shelling out
- 1Password CLI integration for zero-plaintext secret management
- Embedded Butane→Ignition transpilation (CoreOS Butane library) + kubeadm v1beta4 config rendering for Flatcar nodes
- Interactive TUI with rich prompts, spinners, and progress indicators
- Full test suite with unit and integration tests
See the dedicated CLI guide at cmd/homeops-cli/README.md for current operator workflows, and the supporting docs for testing, code review findings, and coverage notes.
cmd/homeops-cli/
├── cmd/ # CLI commands (bootstrap, flatcar, volsync, kubernetes, workstation)
├── internal/ # packages: proxmox, truenas, flatcar, ssh, iso, config, security, ui, ...
├── main.go # Cobra root command with signal handling
└── Makefile # Build, test, lint, coverage
The Kubernetes cluster is deployed using Flatcar Container Linux with kubeadm on Proxmox VE 9.2 VMs. Primary storage is provided by scale-csi, a purpose-built CSI driver that provisions ZFS-backed NVMe-oF/TCP, iSCSI, and NFS volumes on a TrueNAS SCALE appliance (NVMe SLOG-backed, sub-millisecond commit latency), with VolSync/Kopia handling per-app backup and restore.
- Hypervisor: Proxmox VE 9.2 with KVM/QEMU virtualization
- Primary Storage: scale-csi (NVMe-oF/TCP, iSCSI, NFS) on TrueNAS SCALE —
scale-nvmeofis the default StorageClass; secure-by-default NVMe host-NQN allowlisting; driver-side orphan GC - Local Storage: OpenEBS hostpath provisioner on a dedicated 4-drive ZFS RAID10 SSD pool (
openebs-ssd) on the Proxmox host — high-throughput scratch for download landing zones and VolSync mover caches - Network Infrastructure:
- 4x 10GbE Intel X540 NICs bonded via IEEE 802.3ad LACP (40Gbps) on the Proxmox host
- Cisco switch providing high-speed interconnect
- Jumbo frames (MTU 9000) enabled end-to-end
- OS / Distribution: Flatcar Container Linux (immutable, auto-updating) bootstrapped with kubeadm (v1beta4); Kubernetes v1.36.1. The kubelet/kubeadm/kubectl/CNI binaries are delivered as a systemd-sysext image and version-managed by systemd-sysupdate.
- Control-plane endpoint: kube-vip (ARP/L2) VIP
192.168.123.253:6443— CNI-independent, so it is usable as thekubeadmcontrol-plane endpoint during init/join. - VM Configuration: 3 control plane nodes, each with 16 vCPUs, 96GB RAM, and NUMA-pinned CPU affinity
- Storage Strategy: Multiple storage tiers per VM:
- Boot Disk: 100GB VirtIO SCSI disk on the
nvme-mirrorZFS RAID1 for the Flatcar OS (/dev/sda) - Local Scratch: 700GB VirtIO SCSI zvol from the
openebs-ssdZFS RAID10 pool, mounted at/var/mnt/local-hostpathfor OpenEBS hostPath workloads (/dev/sdc) - App Volumes: attached on demand by scale-csi as NVMe-oF/TCP (or iSCSI) block devices from TrueNAS — no static data disks
- Boot Disk: 100GB VirtIO SCSI disk on the
- Networking:
- Cilium CNI with eBPF datapath
- kgateway (Gateway API) for ingress with L2/BGP announcements
- VirtIO network adapters: 16 queues on the primary NIC, 8 on VLAN 90 and the storage fabric, and no queues key on VLAN 20
- Network interface:
eth0(Flatcar names the Proxmox VirtIO NICeth0, notens18)
- Guest Integration: QEMU Guest Agent for enhanced VM management
- Ingress: kgateway (Gateway API) with Cilium L2/BGP LoadBalancer services
- DNS: external-dns for Cloudflare (public) and PowerDNS via RFC2136 (internal) DNS management
- actions-runner-controller: Self-hosted GitHub runners for CI/CD workflows.
- cert-manager: Automated TLS certificate management with Google Trust Services.
- cilium: eBPF-based networking, security, and L2/BGP announcements for LoadBalancer IP allocation.
- cloudflared: Secure tunnels to Cloudflare for external access via Cloudflare Tunnel.
- kgateway: Gateway API controller using Envoy proxy for ingress routing and traffic management.
- external-dns: Automated DNS record management with Cloudflare and PowerDNS (RFC2136) integration.
- external-secrets: Kubernetes External Secrets Operator with 1Password Connect integration.
- flux: GitOps continuous delivery for Kubernetes with SOPS decryption support.
- openebs: Local persistent volume provisioner for hostPath scratch storage on the dedicated
openebs-ssdZFS RAID10 pool. - scale-csi: Primary storage — a purpose-built TrueNAS SCALE CSI driver (WebSocket API, zero SSH) providing NVMe-oF/TCP, iSCSI, and NFS StorageClasses with snapshots, detached clones, expansion, and a driver-side orphan reconciler.
- sops: Managed secrets for Kubernetes using age encryption, committed to Git.
- spegel: Stateless cluster local OCI registry mirror for improved image pull performance.
- kured: Coordinates safe, one-at-a-time node reboots (GitOps-managed) when the Kubernetes systemd-sysext patch updates or a Flatcar OS update have staged a reboot — Flatcar's
locksmithdis masked, so kured is the reboot orchestrator. Minor Kubernetes upgrades are driven via system-upgrade-controller (seeapps/system-upgrade). - volsync: Backup and recovery of persistent volume claims with Kopia.
Flux provides GitOps continuous delivery, watching the kubernetes folder and applying changes based on Git repository state. The setup includes:
- SOPS Integration: Automatic decryption of secrets using age encryption
- Dependency Management: HelmReleases and Kustomizations with explicit dependencies
- Multi-tenancy: Namespace isolation with proper RBAC
- Webhook Integration: GitHub webhook receiver for immediate sync on push
The workflow recursively searches the kubernetes/apps folder for kustomization.yaml files, which typically contain namespace resources and Flux Kustomizations (ks.yaml). Each Kustomization manages HelmReleases or other Kubernetes resources for applications.
Renovate provides automated dependency management across the entire repository, creating pull requests for updates to:
- Container images with digest pinning
- Helm chart versions
- Kubernetes manifests
- GitHub Actions workflows
This Git repository is organized for GitOps workflows and infrastructure management:
📁 home-ops
├── 📁 bootstrap # Initial cluster bootstrap resources
├── 📁 kubernetes
│ ├── 📁 apps # Application deployments by namespace
│ │ ├── 📁 actions-runner-system # Self-hosted GitHub runners
│ │ ├── 📁 automation # Workflow automation (n8n)
│ │ ├── 📁 cert-manager # Certificate management
│ │ ├── 📁 downloads # Media acquisition stack
│ │ ├── 📁 external-secrets # Secret management
│ │ ├── 📁 flux-system # Flux controllers
│ │ ├── 📁 kube-system # Core Kubernetes components
│ │ ├── 📁 media # Media serving applications
│ │ ├── 📁 network # Networking applications
│ │ ├── 📁 observability # Monitoring and logging
│ │ ├── 📁 openebs-system # Local storage provisioner
│ │ ├── 📁 scale-csi # TrueNAS CSI driver (NVMe-oF/iSCSI/NFS)
│ │ ├── 📁 self-hosted # Productivity and tools
│ │ ├── 📁 system-upgrade # Automated upgrades
│ │ └── 📁 volsync-system # Volume backup and recovery
│ ├── 📁 components # Reusable Kustomize components
│ │ ├── 📁 alerts # AlertManager configurations
│ │ ├── 📁 cluster-secret # Cluster-wide secrets
│ │ ├── 📁 nfs-scaler # NFS availability scaling
│ │ └── 📁 volsync-direct # Direct volume backup/restore
│ └── 📁 flux # Flux system configuration
├── 📁 cmd # HomeOps CLI source code
│ └── 📁 homeops-cli # Go-based automation tool
└── 📁 scripts # Automation and utility scriptsNode OS + kubeadm configuration (Butane/Ignition, kubeadm v1beta4, kube-vip) lives as embedded templates inside
cmd/homeops-cli, not a top-level directory.
This is a high-level look how Flux deploys my applications with dependencies. In most cases a HelmRelease will depend on other HelmRelease's, in other cases a Kustomization will depend on other Kustomization's, and in rare situations an app can depend on a HelmRelease and a Kustomization. The example below shows an app whose persistent volume is provisioned by scale-csi and backed up by VolSync — the app's PVC is created with a dataSourceRef to a VolSync ReplicationDestination, so data restores automatically on (re)creation.
graph TD
A>Kustomization: scale-csi] -->|Creates| B[HelmRelease: scale-csi]
C>Kustomization: volsync] -->|Creates| D[HelmRelease: volsync]
E>Kustomization: atuin] -->|Creates| F(HelmRelease: atuin)
E>Kustomization: atuin] -->|Creates| G(PVC: atuin on scale-nvmeof)
G>PVC: atuin] -->|Provisioned by| B>HelmRelease: scale-csi]
F>HelmRelease: atuin] -->|Backed up by| D>HelmRelease: volsync]
The repository includes comprehensive automation for cluster management through a custom Go-based CLI:
A purpose-built Go application that provides complete infrastructure automation:
Core Capabilities:
- Bootstrap: Complete cluster initialization (
--provider flatcar: kubeadm init/join over SSH, then Cilium/CRDs/Flux) with preflight checks and 1Password integration - Flatcar Provisioning: Butane→Ignition rendering, kubeadm config generation, and
kubeadm init/joinorchestration over SSH - VM Management: Proxmox VE 9.2 VM creation booting Flatcar images with Ignition injected via qemu fw_cfg
- Volume Operations: VolSync-based backup and restore with Kopia integration
- Kubernetes Management: Deployment restarts, PVC browsing, and maintenance operations
Key Commands:
# Bootstrap entire cluster
homeops-cli bootstrap
# Flatcar node provisioning + cluster bootstrap (kubeadm)
homeops-cli flatcar deploy-vm --nodes k8s-0,k8s-1,k8s-2 --image-path /var/lib/vz/template/flatcar.img --concurrent 3
homeops-cli flatcar render-ignition --node k8s-0 # inspect rendered Ignition
homeops-cli bootstrap --provider flatcar # kubeadm init/join + Cilium/CRDs/Flux
# Kubernetes and Flux operations
homeops-cli k8s view-secret
homeops-cli k8s apply-ks ./kubernetes/apps/observability/grafana/ks.yaml --name grafana-instance
# Volume backup/restore
homeops-cli volsync snapshot --pvc data-pvc --namespace default
homeops-cli volsync restore --pvc data-pvc --namespace defaultSupporting Tools:
- Template Rendering: Embedded Butane→Ignition transpilation (CoreOS Butane) + kubeadm v1beta4 configs for Flatcar
- Secret Injection: 1Password CLI integration for secure secret management
- Environment Management: mise for tool and environment variable management
- Configuration Validation: Pre-commit hooks with kubeconform and YAML linting
- CI/CD: GitHub Actions for automated testing, schema validation, and deployment
While most infrastructure and workloads are self-hosted, I rely on cloud services for critical functions to avoid chicken/egg scenarios and ensure availability of essential services regardless of cluster state. This approach balances self-hosting benefits with operational reliability.
Alternative solutions would involve running a separate cloud-hosted Kubernetes cluster for critical services like Vault, Vaultwarden, or ntfy, but the operational overhead and costs would likely exceed the current cloud service expenses.
| Service | Use | Cost |
|---|---|---|
| 1Password | Secrets with External Secrets | ~$65/yr |
| Cloudflare | Domain, DNS, and tunnel services | ~$30/yr |
| Google Workspace | Email hosting and productivity suite | ~$72/yr |
| GitHub | Repository hosting and CI/CD with Actions | Free |
| iLert | Incident management and alerting | Free (tier) |
| Pushover | Mobile notifications for alerts | $5 OTP |
| Total: ~$14/mo |
The cluster implements a sophisticated networking architecture using Cilium and kgateway (Gateway API):
- Cloudflare Tunnel: Secure external access via
cloudflaredwithout port forwarding - External DNS (Cloudflare): Automatic DNS record management in Cloudflare for public services
- Gateway API: kgateway-based ingress with dedicated LoadBalancer IPs per application
- External DNS (PowerDNS): external-dns deployment using RFC2136 provider for internal DNS record management via PowerDNS
- CoreDNS: Kubernetes cluster DNS with custom configurations
- Cilium Announcements: Cilium L2/BGP announcements for LoadBalancer IP allocation
- Physical Layer:
- 40Gbps LACP bond aggregating 4x 10GbE Intel X540 NICs
- IEEE 802.3ad Link Aggregation Control Protocol (LACP)
- Jumbo frames (MTU 9000) enabled end-to-end
- Bidirectional bandwidth between Proxmox host and TrueNAS Scale
- CNI: Cilium with eBPF datapath for high-performance networking
- Load Balancing: Maglev algorithm with DSR (Direct Server Return) mode
- IP Management: Kubernetes IPAM with native routing (Pod CIDR: 10.42.0.0/16)
- BGP Peering:
- Cilium ASN: 64550
- Cisco C9300 ASN: 64541
- LoadBalancer IP pool: 192.168.255.0/24
- Gateway IPs: LoadBalancer services advertised via L2 and BGP
- Kernel Optimizations:
- TCP Congestion Control: BBR
- TCP Buffer Sizes: 64MB max
- Socket Buffers: 128MB (rcvbuf/sndbuf)
- NFS nconnect: 16 parallel connections
The cluster hosts a variety of self-hosted applications organized by namespace and function:
| Application | Purpose | Access |
|---|---|---|
| Atuin | Shell history sync | sh.${SECRET_DOMAIN} |
| OCIS | Personal file sync & sharing | ocis.${SECRET_DOMAIN} |
| The Lounge | Persistent IRC/web chat client | thelounge.${SECRET_DOMAIN} |
| Application | Purpose | Access |
|---|---|---|
| Actual | Personal budgeting | actual.${SECRET_DOMAIN} |
| FreshRSS | RSS feed aggregator | feeds.${SECRET_DOMAIN} |
All self-hosted apps now share the self-hosted namespace so VolSync movers and Kopia ownership stay aligned (snapshots live under identities like app@self-hosted:/data).
| Application | Purpose | Access |
|---|---|---|
| Jellyseerr | Media discovery & request management | requests.${SECRET_DOMAIN} |
Media workloads live in the media namespace so VolSync and Kopia identities follow app@media:/data for consistent restores.
| Application | Purpose | Access |
|---|---|---|
| Autobrr | Real-time announce filtering & actions | autobrr.${SECRET_DOMAIN} |
| Bazarr | Subtitle management for Radarr/Sonarr libraries | bazarr.${SECRET_DOMAIN} |
| Cross-Seed | Torrent cross-seeding suggestions | Internal only |
| NZBGet | Usenet downloader | nzbget.${SECRET_DOMAIN} |
| Pinchflat | Long-form video & podcast archiving | pinchflat.${SECRET_DOMAIN} |
| Prowlarr | Indexer proxy & search aggregator | prowlarr.${SECRET_DOMAIN} |
| qBittorrent | VPN-protected torrent client with VueTorrent UI | qbittorrent.${SECRET_DOMAIN} |
| Qui | Autobrr queue monitor & dashboard | qui.${SECRET_DOMAIN} |
| Radarr | Movie library automation | radarr.${SECRET_DOMAIN} |
| Recyclarr | Radarr/Sonarr config synchronisation | Internal only |
| TQM | Automated qBittorrent retag/cleanup jobs | Internal only |
| Sonarr | TV library automation | sonarr.${SECRET_DOMAIN} |
The entire download stack now lives in the downloads namespace so VolSync movers and Kopia ownership stay aligned (app@downloads:/data) and restores remain consistent.
| Application | Purpose | Access |
|---|---|---|
| n8n | Workflow automation & integrations | n8n.${SECRET_DOMAIN} |
Automation workloads run in the automation namespace so VolSync restores and Kopia ownership continue to match app@automation:/data.
Fully native VictoriaMetrics stack — single vendor, native protocols, no translation layers.
| Application | Purpose | Access |
|---|---|---|
| VMSingle | Metrics storage (300Gi, 14d retention) | metrics.${SECRET_DOMAIN} |
| VMAgent | Metrics scraping with streaming aggregation | Internal only |
| VMAuth | Unified auth proxy (host + path routing) | Routes all external/Grafana traffic |
| VMAlert | Metrics + log alerting (2 instances) | Internal only |
| VMAlertManager | Alert routing to Pushover | alertmanager.${SECRET_DOMAIN} |
| VictoriaLogs | Log storage with native syslog ingestion (500Gi, 14d) | logs.${SECRET_DOMAIN} |
| vlagent | K8s pod log collection (DaemonSet) | Internal only |
| vmbackup | Daily incremental metrics backup to NFS | Internal only |
| Grafana | Dashboards via grafana-operator | grafana.${SECRET_DOMAIN} |
| Blackbox Exporter | ICMP/HTTP/TCP probing | Internal only |
| Gatus | Uptime monitoring | status.${SECRET_DOMAIN} |
| KEDA | Event-driven autoscaling | Internal only |
Log pipeline: Pod logs → vlagent (native protocol) → VictoriaLogs. Network syslog (Cisco/VyOS/UniFi) → VictoriaLogs native syslog listener (UDP 514/5514/5515 on LB 192.168.255.254). No fluent-bit, no vector.
| Application | Purpose | Access |
|---|---|---|
| scale-csi | Primary storage — TrueNAS NVMe-oF/iSCSI/NFS CSI driver | Internal only |
| OpenEBS | Local persistent volume provisioner (ZFS RAID10 scratch) | Internal only |
All applications use kgateway (Gateway API) for ingress with automatic TLS certificates from Google Trust Services via cert-manager.
The cluster uses a multi-tier storage architecture with scale-csi (TrueNAS SCALE) as the primary storage layer:
| Tier | Provider | StorageClass | Use Case |
|---|---|---|---|
| NVMe-oF Block | scale-csi | scale-nvmeof (default) |
Application PVCs — highest IOPS, sub-ms commit latency |
| iSCSI Block | scale-csi | scale-iscsi |
Block volumes over iSCSI |
| NFS Filesystem | scale-csi | scale-nfs |
Shared (RWX-capable) storage |
| Local Scratch | OpenEBS | openebs-hostpath |
Download landing zones + VolSync caches on node-local ZFS RAID10 |
| Backup | VolSync + Kopia | — | Automated PVC backup and restore |
scale-csi is a purpose-built CSI driver for TrueNAS SCALE, talking exclusively to its WebSocket JSON-RPC API (zero SSH):
- Backend: TrueNAS SCALE all-flash ZFS pool with mirrored NVMe SLOG (~1ms fsync commit latency) and NVMe L2ARC
- Protocols: NVMe-oF/TCP (primary), iSCSI, and NFS from a single driver
- Security: NVMe subsystem host-NQN allowlisting (secure-by-default, no
allowAnyHost), non-root controller, path-traversal-hardened volume IDs - Data safety: detached (fully independent) volumes from snapshots, foreign-snapshot deletion guards, and a driver-side orphan reconciler (read-only detection with Prometheus gauges + a gated, capped nightly GC)
- Features: snapshots/clones/expansion, VolumeSnapshotClass
scale-snapshot, online NVMe-oF expansion, integrated metrics + alerts
VolSync with Kopia provides automated backup:
- ReplicationSource: Scheduled backups of PVCs to S3-compatible storage
- ReplicationDestination: Point-in-time recovery with dataSource references
- Identity Alignment: Namespace-based identity (
app@namespace:/data) for consistent restores
| Component | Specifications | Function |
|---|---|---|
| Proxmox Host | Proxmox VE 9.2 (KVM/QEMU) | VM compute & management |
| ├─ CPU | 2x Intel Xeon E5-2697A v4 @ 2.60GHz (32 cores / 64 threads) | VM compute resources |
| ├─ Memory | 512GB DDR4-2400 ECC (16x 32GB) | VM memory allocation |
| ├─ Network | 4x 10GbE Intel X540 NICs (40Gbps LACP to Cisco switch) | High-speed VM networking |
| └─ Storage | 2x NVMe SSDs (ZFS RAID1 mirror — VM boot, 30GB/drive over-provisioned) + 4x 1TB SSD (ZFS RAID10 openebs-ssd, 50GB/drive over-provisioned) |
VM boot mirror + local scratch zvols |
| Storage Server | TrueNAS Scale | iSCSI, NVMe-oF & NFS storage |
| ├─ CPU | 2x Intel Xeon E5-2690 v4 @ 2.60GHz (28 cores / 56 threads) | Storage processing |
| ├─ Memory | 120GB DDR4-2400 ECC (8x 16GB, reduced for VM allocation) | ZFS ARC cache and services |
| ├─ L2ARC | 2x 1TB NVMe (1.8TB read cache) | Extended read cache |
| ├─ SLOG | 2x 60GB NVMe (mirrored) | Synchronous write log |
| ├─ Network | 4x 10GbE Intel X540 NICs (40Gbps LACP to Cisco switch) | Storage network |
| └─ Protocols | iSCSI (block) + NVMe-oF (block) + NFS 4.2 (file) | Primary k8s storage via scale-csi |
| Network Switch | Cisco C9300 | Infrastructure interconnect |
| ├─ LACP Configuration | IEEE 802.3ad Link Aggregation (40Gbps total) | High-bandwidth storage path |
| ├─ BGP Peering | AS 64541 (peering with Cilium AS 64550) | LoadBalancer IP advertisement |
| └─ MTU | Jumbo frames (9000 bytes) enabled | Optimized large frame throughput |
| Storage Tier | Hardware | Purpose |
|---|---|---|
| TrueNAS Primary Pool | 3x RAIDZ1 vdevs (3 disks each = 28.5TB usable) | scale-csi volumes (NVMe-oF/iSCSI zvols + NFS) + media exports |
| SLOG (Intent Log) | 2x 60GB NVMe (mirrored) | Synchronous write acceleration |
| L2ARC (Read Cache) | 2x 1TB NVMe (1.8TB total) | Extended ARC read cache |
| VM Role | Count | vCPU | Memory | Storage Layout | OS |
|---|---|---|---|---|---|
| Kubernetes Control Plane | 3 | 16 | 96GB | 100GB boot (nvme-mirror RAID1) + 700GB local scratch (openebs-ssd RAID10 zvol) | Flatcar Container Linux + kubeadm (k8s v1.36.1) |
Storage Details:
- Boot Disk (
scsi0,/dev/sda): 100GB VirtIO SCSI disk on thenvme-mirrorZFS RAID1 for the Flatcar OS - Local Scratch (
scsi3,/dev/sdc): 700GB VirtIO SCSI zvol from theopenebs-ssdZFS RAID10 pool (ext4,LABEL=openebs-local), mounted at/var/mnt/local-hostpathfor OpenEBS hostPath workloads - App Volumes: attached dynamically by scale-csi as NVMe-oF/TCP (or iSCSI) devices from TrueNAS — no static data disks
VM Configuration:
- Provisioning: Ignition via qemu fw_cfg (
opt/org.flatcar-linux/config) — the rendered Ignition is attached at VM create; the disk imports a Flatcar image (no install ISO) - BIOS: OVMF (UEFI)
- CPU Type:
host,flags=+pdpe1gb;-spec-ctrl - Network: VirtIO on
vmbr0; primarynet0uses MTU 9000, VLAN 999, and 16 queues; VLAN 20net1uses MTU 1500 with no queues key; VLAN 90net2uses MTU 1500 and 8 queues; storagenet3-net6use MTU 9000 and 8 queues - Guest Agent: QEMU Guest Agent for enhanced management
- Machine Type: Q35 chipset
CPU & Memory Optimization:
- NUMA Pinning: Each VM is pinned to specific CPU cores with HT siblings for optimal memory locality
- k8s-0 (VMID 200): Cores 0-7,32-39 (Socket 0, NUMA 0)
- k8s-1 (VMID 201): Cores 16-23,48-55 (Socket 1, NUMA 1)
- k8s-2 (VMID 202): Cores 8-15,40-47 (Socket 0, NUMA 0)
- Memory: 96GB (98304 MB) per VM, no overcommit
Total VM Resources: 48 vCPUs, 288GB RAM allocated from the 64-thread, 512GB host system.
This homelab serves as a continuous learning platform for cloud-native technologies, GitOps practices, and infrastructure automation. The setup provides hands-on experience with production-grade tools and practices in a controlled environment.
Special thanks to onedr0p and the k8s-at-home community. This repository was heavily inspired by onedr0p's home-ops repository, which served as an excellent learning resource and foundation for understanding GitOps workflows and Kubernetes cluster management.
Thanks to all the people who donate their time to the Home Operations Discord community. Be sure to check out kubesearch.dev for ideas on how to deploy applications or get ideas on what you could deploy.