... a GitOps-driven homelab managed with Flux, Renovate, and GitHub Actions
This mono repository serves as the single source of truth for my home(lab) infrastructure and single-node Kubernetes cluster, following Infrastructure as Code (IaC) and GitOps best practices.
The cluster is automated with the following tools:
- Talos Linux β Immutable, API-driven OS that runs nothing but Kubernetes.
- Flux β Continuous reconciliation of cluster state against this repository.
- Renovate β Automated dependency updates across the entire cluster.
- GitHub Actions β Validation and automation on every commit.
This ensures an immutable and reproducible environment, with changes applied automatically based on repository state.
π bootstrap # One-time cluster bootstrap (helmfile + kustomize)
π docker # Services running via Docker on NAS
π kubernetes # Everything Flux reconciles
ββπ apps # Workloads, grouped by namespace
ββπ components # Reusable Kustomize components (alerts, volsync, etc.)
ββπ flux # Flux system configuration
π talos # Talos machine configs and per-node overridesThe cluster operates on Talos Linux, an immutable and ephemeral Linux distribution tailored for Kubernetes, deployed on bare-metal Minisforum MS-A2 workstation. Persistent storage is provided by OpenEBS with bulk media offloaded to TrueNAS SCALE over NFS.
- actions-runner-controller β Self-hosted GitHub runners for CI/CD workflows.
- cert-manager β Automated SSL certificate management and provisioning.
- cilium β High-performance container networking powered by eBPF.
- cloudflared β Secure tunnel providing Cloudflare-protected access to cluster services.
- envoy-gateway β Modern ingress controller for cluster traffic management.
- external-dns β Automated DNS record synchronization for ingress resources.
- external-secrets β Kubernetes secrets management integrated with 1Password Connect.
- openebs β Persistent container native storage with local Hostpath and ZFS.
- spegel β Stateless cluster-local OCI registry mirror for improved performance.
- volsync β Advanced backup and recovery solution for persistent volume claims.
Flux continuously monitors the Kubernetes clusters defined in the kubernetes directory and reconciles their state with the configuration stored in the Git repository.
To deploy applications, Flux recursively scans the kubernetes/apps directory and identifies the highest-level kustomization.yaml file within each application directory. Each discovered kustomization.yaml is then applied together with all resources it references.
In most cases, these top-level kustomization.yaml files contain a namespace definition and one or more Flux Kustomization resources (ks.yaml). These Flux Kustomizations manage the deployment of application-specific resources, such as HelmRelease objects and any additional Kubernetes manifests required by the application.
Renovate monitors the entire repository for dependency updates. When updates are available, Renovate automatically creates a pull request. Once a pull request is reviewed and merged, Flux reconciles the updated repository state and applies the corresponding changes to the Kubernetes clusters.
The diagram below provides a high-level overview of how Flux manages application deployments and dependency ordering.
In most scenarios, a HelmRelease depends on one or more other HelmRelease resources. In other cases, a Kustomization depends on one or more other Kustomization resources. Less commonly, an application may require a combination of both HelmRelease and Kustomization dependencies.
Flux evaluates these dependencies and ensures that resources are deployed and upgraded in the correct order. A dependent resource will not be reconciled until all of its declared dependencies have been successfully deployed and are reporting a healthy state.
In the example below, the victoria-logs-collector application depends on victoria-logs, which in turn depends on openebs. As a result, openebs must be successfully deployed and healthy before victoria-logs is installed or upgraded. Likewise, victoria-logs must be healthy before victoria-logs-collector can be deployed or updated.
graph LR
classDef kustom fill:#43A047,stroke:#2E7D32,stroke-width:3px,color:#fff,font-weight:bold,rx:10,ry:10
classDef helm fill:#1976D2,stroke:#0D47A1,stroke-width:3px,color:#fff,font-weight:bold,rx:10,ry:10
A["π¦ Kustomization<br/>victoria-logs"]:::kustom
B["π¦ Kustomization<br/>victoria-logs-collector"]:::kustom
C["π¦ Kustomization<br/>openebs"]:::kustom
D["π― HelmRelease<br/>victoria-logs"]:::helm
E["π― HelmRelease<br/>victoria-logs-collector"]:::helm
F["π― HelmRelease<br/>openebs"]:::helm
A -->|Creates| D
B -->|Creates| E
C -->|Creates| F
A -.->|Depends on| C
B -.->|Depends on| A
To be documented...
The cluster runs two separate ExternalDNS instances, each responsible for managing DNS records in a different environment.
The first instance manages public DNS records and synchronizes them to Cloudflare. The second instance synchronizes private DNS records to a UCG Fiber gateway using the ExternalDNS UniFi webhook provider.
This separation is achieved through the use of two ingress classes:
internalβ Used for services that require private DNS records.externalβ Used for services that require public DNS records.
Based on the assigned ingress class, the corresponding ExternalDNS instance automatically discovers the ingress resources and creates or updates DNS records on the appropriate platform. This approach provides clear separation between internal and external services while ensuring that DNS records remain synchronized with the cluster's configuration.
While most services are self-hosted, certain critical components rely on cloud services to ensure availability, especially in bootstrapping scenarios.
| Service | Purpose | Cost |
|---|---|---|
| 1Password | Secret management via External Secrets | ~β¬40/yr |
| Cloudflare | DNS and secure access via Cloudflare Tunnel | Free |
| GitHub | Repository hosting and CI/CD | Free |
| Mailgun | Automatic email delivery | Free (Flex Plan) |
| Name.com | Domain registration | ~β¬55/yr |
| Pushover | Infrastructure alerts and notifications | $5 (One Time Fee) |
| Tailscale | Secure private remote access | Free |
| ~β¬8/mo |
| Device | Count | OS Disk | Data Disk | RAM | OS | Function |
|---|---|---|---|---|---|---|
| Minisforum MS-A2 | 1 | 1.92TB M.2 | 3.84TB U.2, 1.92TB M.2 | 96GB | Talos | Kubernetes |
| Inter-Tech 4U-4416 | 1 | 2x1TB M.2 | 2x1TB SSD, 4x12TB HDD, 4x8TB HDD | 256GB | TrueNAS SCALE | NFS & Backup Storage |
| PiKVM V4 Plus | 1 | - | - | - | - | KVM for Kubernetes |
| UniFi Cloud Gateway Fiber | 1 | - | - | - | UniFi OS | Firewall & Router |
| UniFi Aggregation | 1 | - | - | - | UniFi OS | 10G SFP+ Switch |
| UniFi Pro 24 PoE | 1 | - | - | - | UniFi OS | 1GbE PoE+ Switch |
| UniFi AC Pro | 2 | - | - | - | UniFi OS | WiFi 5 Access Point |
- CPU: AMD Ryzenβ’ 9 9955HX
- RAM: Crucial 96GB (2x48GB) DDR5-5600 SODIMM CT2K48G56C46S5
- M.2 Storage: Samsung PM9A3 1.92TB M.2 22110 NVMe PCIe 4.0 x4 MZ1L21T9HCLS
- U.2 Storage: Samsung PM9A3 3.84TB U.2 NVMe PCIe 4.0 x4 MZQL23T8HCLS
- Case: Minisforum MS-A2
- CPU: AMD EPYCβ’ 7002 (Rome)
- RAM: Samsung 256GB (8x32GB) DDR4-2666 ECC RDIMM M393A4K40CB2-CTD
- Motherboard: Supernicro H12SSL-i
- M.2 Storage: Samsung 980 PRO 1TB M.2 2280 NVMe PCIe 4.0 x4 MZ-V8P1T0BW
- SSD Storage: WD Red SA500 NAS 1TB SATA SSD WDS100T1R0A
- HDD Storage: WD Red Pro 12TB SATA 7200 RPM 256MB Cache WD120EFBX
- HDD Storage: WD Red Plus 8TB SATA 5400 RPM 256MB Cache WD80EFAX
- Storage Controller: ServeRAID M1215 SAS/SATA Controller
- Case: Inter-Tech 4U-4416
A special thanks to the Home Operations Discord community for their insights and inspiration. Many ideas stem from shared clusters under the k8s-at-home GitHub topic and the excellent Kubesearch tool.
See LICENSE