{"id":43182951,"url":"https://github.com/zebernst/homelab","last_synced_at":"2026-03-01T04:10:43.790Z","repository":{"id":247702365,"uuid":"822693184","full_name":"zebernst/homelab","owner":"zebernst","description":"Cat-approved 😸 Home Kubernetes cluster running Talos Linux | Automated via Flux, Renovate, and GitHub Actions ⚙️","archived":false,"fork":false,"pushed_at":"2026-02-24T05:02:01.000Z","size":2597,"stargazers_count":28,"open_issues_count":97,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-02-24T07:56:53.352Z","etag":null,"topics":["flux","gitops","homelab","iac","k8s-at-home","kubernetes","renovate","selfhosted","talos"],"latest_commit_sha":null,"homepage":"https://status.zebernst.dev","language":"YAML","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/zebernst.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2024-07-01T16:13:45.000Z","updated_at":"2026-02-24T02:32:40.000Z","dependencies_parsed_at":"2024-09-13T11:02:02.656Z","dependency_job_id":"10690b2b-3c60-4ae6-983d-29241ca35e6a","html_url":"https://github.com/zebernst/homelab","commit_stats":null,"previous_names":["zebernst/homelab"],"tags_count":15,"template":false,"template_full_name":"onedr0p/cluster-template","purl":"pkg:github/zebernst/homelab","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zebernst%2Fhomelab","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zebernst%2Fhomelab/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zebernst%2Fhomelab/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zebernst%2Fhomelab/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/zebernst","download_url":"https://codeload.github.com/zebernst/homelab/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zebernst%2Fhomelab/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29960238,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-01T01:47:18.291Z","status":"online","status_checked_at":"2026-03-01T02:00:07.437Z","response_time":124,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["flux","gitops","homelab","iac","k8s-at-home","kubernetes","renovate","selfhosted","talos"],"created_at":"2026-02-01T04:02:20.607Z","updated_at":"2026-03-01T04:10:43.713Z","avatar_url":"https://github.com/zebernst.png","language":"YAML","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n\n\u003cimg src=\"https://github.com/user-attachments/assets/0248f379-cc4a-4a59-a400-014a750c61fa\" align=\"center\" width=\"144px\" height=\"144px\"/\u003e\n\n\n### My homelab k8s cluster \u003cimg src=\"https://fonts.gstatic.com/s/e/notoemoji/latest/2728/512.gif\" alt=\"🚀\" width=\"16\" height=\"16\"\u003e\n\n_... automated via [Flux](https://github.com/fluxcd/flux2), [Renovate](https://github.com/renovatebot/renovate) and [GitHub Actions](https://github.com/features/actions)_ \u003cimg src=\"https://fonts.gstatic.com/s/e/notoemoji/latest/1f916/512.gif\" alt=\"🤖\" width=\"16\" height=\"16\"\u003e\n\n\u003c/div\u003e\n\n\u003cdiv align=\"center\"\u003e\n\n[![Talos](https://img.shields.io/endpoint?url=https%3A%2F%2Fkromgo.zebernst.dev%2Ftalos_version\u0026style=for-the-badge\u0026logo=talos\u0026logoColor=white\u0026color=blue\u0026label=%20)](https://talos.dev)\u0026nbsp;\u0026nbsp;\n[![Kubernetes](https://img.shields.io/endpoint?url=https%3A%2F%2Fkromgo.zebernst.dev%2Fkubernetes_version\u0026style=for-the-badge\u0026logo=kubernetes\u0026logoColor=white\u0026color=blue\u0026label=%20)](https://kubernetes.io)\u0026nbsp;\u0026nbsp;\n[![Flux](https://img.shields.io/endpoint?url=https%3A%2F%2Fkromgo.zebernst.dev%2Fflux_version\u0026style=for-the-badge\u0026logo=flux\u0026logoColor=white\u0026color=blue\u0026label=%20)](https://fluxcd.io)\u0026nbsp;\u0026nbsp;\n[![Renovate](https://img.shields.io/github/actions/workflow/status/zebernst/homelab/renovate.yaml?branch=main\u0026label=\u0026logo=renovate\u0026style=for-the-badge\u0026color=blue)](https://github.com/zebernst/homelab/actions/workflows/renovate.yaml)\n\n\u003c/div\u003e\n\n\u003cdiv align=\"center\"\u003e\n\n[![Home-Internet](https://img.shields.io/uptimerobot/status/m798207245-e361357b4ae0adccce695dd9?style=for-the-badge\u0026logo=ubiquiti\u0026logoColor=white\u0026logoSize=auto\u0026label=Home%20Internet\u0026color=brightgreen)](https://status.zebernst.dev)\u0026nbsp;\u0026nbsp;\n[![Status-Page](https://img.shields.io/uptimerobot/status/m798207270-986abc3b5ee42d8f51b9fc5c?color=brightgreeen\u0026label=Status%20Page\u0026style=for-the-badge\u0026logo=statuspage\u0026logoColor=white)](https://status.zebernst.dev)\u0026nbsp;\u0026nbsp;\n[![Alertmanager](https://img.shields.io/endpoint?url=https%3A%2F%2Fhealthchecks.io%2Fb%2F2%2F75190d22-c52a-410d-9f45-9bd7c95cbb96.shields?color=brightgreeen\u0026label=Alertmanager\u0026style=for-the-badge\u0026logo=prometheus\u0026logoColor=white)](https://status.zebernst.dev)\n\u003c/div\u003e\n\n\u003cdiv align=\"center\"\u003e\n\n[![Age-Days](https://img.shields.io/endpoint?url=https%3A%2F%2Fkromgo.zebernst.dev%2Fcluster_age_days\u0026style=flat-square\u0026label=Age)](https://github.com/kashalls/kromgo)\u0026nbsp;\u0026nbsp;\n[![Uptime-Days](https://img.shields.io/endpoint?url=https%3A%2F%2Fkromgo.zebernst.dev%2Fcluster_uptime_days\u0026style=flat-square\u0026label=Uptime)](https://github.com/kashalls/kromgo)\u0026nbsp;\u0026nbsp;\n[![Node-Count](https://img.shields.io/endpoint?url=https%3A%2F%2Fkromgo.zebernst.dev%2Fcluster_node_count\u0026style=flat-square\u0026label=Nodes)](https://github.com/kashalls/kromgo)\u0026nbsp;\u0026nbsp;\n[![Pod-Count](https://img.shields.io/endpoint?url=https%3A%2F%2Fkromgo.zebernst.dev%2Fcluster_pod_count\u0026style=flat-square\u0026label=Pods)](https://github.com/kashalls/kromgo)\u0026nbsp;\u0026nbsp;\n[![CPU-Usage](https://img.shields.io/endpoint?url=https%3A%2F%2Fkromgo.zebernst.dev%2Fcluster_cpu_usage\u0026style=flat-square\u0026label=CPU)](https://github.com/kashalls/kromgo)\u0026nbsp;\u0026nbsp;\n[![Memory-Usage](https://img.shields.io/endpoint?url=https%3A%2F%2Fkromgo.zebernst.dev%2Fcluster_memory_usage\u0026style=flat-square\u0026label=Memory)](https://github.com/kashalls/kromgo)\u0026nbsp;\u0026nbsp;\n[![Power-Usage](https://img.shields.io/endpoint?url=https%3A%2F%2Fkromgo.zebernst.dev%2Fcluster_power_usage\u0026style=flat-square\u0026label=Power)](https://github.com/kashalls/kromgo)\n\n\u003c/div\u003e\n\n---\n\n## \u003cimg src=\"https://fonts.gstatic.com/s/e/notoemoji/latest/1f4a1/512.gif\" alt=\"✏\" width=\"20\" height=\"20\"\u003e Overview\n\nThis is a repository for my home infrastructure and Kubernetes cluster. I try to adhere to Infrastructure as Code (IaC) and GitOps practices using tools like [Kubernetes](https://github.com/kubernetes/kubernetes), [Flux](https://github.com/fluxcd/flux2), [Renovate](https://github.com/renovatebot/renovate) and [GitHub Actions](https://github.com/features/actions).\n\n---\n\n## \u003cimg src=\"https://fonts.gstatic.com/s/e/notoemoji/latest/1f331/512.gif\" alt=\"🌱\" width=\"20\" height=\"20\"\u003e Kubernetes\n\nThis hyper-converged cluster runs [Talos Linux](https://github.com/siderolabs/talos), an immutable and ephemeral Linux distribution tailored for [Kubernetes](https://github.com/kubernetes/kubernetes), and is deployed on bare-metal Minisforum MS-01 mini-PCs. Currently, persistent storage is provided via [Rook](https://github.com/rook/rook) in order to enable resilient block-, file-, and object-storage within the cluster. A Synology NAS handles media file storage and backups, and is also available as an alternate storage location with the help of a [custom fork](https://github.com/zebernst/synology-csi-talos) of the official Synology CSI for workloads that should not be hyper-converged. The cluster is designed to enable a full teardown without any data loss.\n\n🔸 _[Click here](./kubernetes/bootstrap/talos/machineconfig.yaml.j2) to see my Talos configuration._\n\nThere is a template at [onedr0p/cluster-template](https://github.com/onedr0p/cluster-template) if you want to follow along with many of the practices I use here.\n\n### Core Components\n\n[//]: # (- [actions-runner-controller]\u0026#40;https://github.com/actions/actions-runner-controller\u0026#41;: Self-hosted Github runners.)\n- [cert-manager](https://github.com/cert-manager/cert-manager): Manage SSL certificates for services in my cluster.\n- [cilium](https://github.com/cilium/cilium): eBPF-based networking for my workloads.\n- [cloudflared](https://github.com/cloudflare/cloudflared): Enables Cloudflare secure access to my services.\n- [external-dns](https://github.com/kubernetes-sigs/external-dns): Automatically syncs ingress DNS records to a DNS provider.\n- [external-secrets](https://github.com/external-secrets/external-secrets): Managed Kubernetes secrets using [1Password Connect](https://github.com/1Password/connect).\n- [ingress-nginx](https://github.com/kubernetes/ingress-nginx): Kubernetes ingress controller using NGINX as a reverse proxy and load balancer.\n- [rook](https://github.com/rook/rook): Distributed block, file, and object storage for stateful workloads.\n- [spegel](https://github.com/spegel-org/spegel): Stateless cluster-local OCI registry mirror.\n- [volsync](https://github.com/backube/volsync): Backup and recovery of persistent volume claims.\n\n### GitOps\n\n[Flux](https://github.com/fluxcd/flux2) monitors my [kubernetes](./kubernetes) folder (see Directories below) and implements changes to my cluster based on the YAML manifests.\n\nFlux operates by recursively searching the [kubernetes/apps](./kubernetes/apps) folder until it locates the top-level `kustomization.yaml` in each directory. It then applies all the resources listed in it. This `kustomization.yaml` typically contains a namespace resource and one or more Flux kustomizations. These Flux kustomizations usually include a `HelmRelease` or other application-related resources, which are then applied.\n\n[Renovate](https://github.com/renovatebot/renovate) monitors my **entire** repository for dependency updates, automatically creating a PR when updates are found. When the relevant PRs are merged, [Flux](https://github.com/fluxcd/flux2) then applies the changes to my cluster.\n\n### Directories\n\nThis Git repository contains the following directories under [kubernetes/](./kubernetes).\n\n```sh\n📁 kubernetes\n├── 📁 apps           # applications\n├── 📁 bootstrap      # bootstrap procedures\n├── 📁 components     # reusable kustomize components\n└── 📁 flux           # core flux configuration\n```\n\n### Cluster layout\n\nThis is a high-level look how Flux deploys my applications with dependencies. Below there are 3 Flux kustomizations `cloudnative-pg`, `postgres-cluster`, and `atuin`. `cloudnative-pg` is the first app that needs to be running and healthy before `postgres-cluster` and once `postgres-cluster` is healthy, then `atuin` will be deployed.\n\n```mermaid\ngraph TD;\n  id1\u003eKustomization: cluster] --\u003e|Creates| id2\u003eKustomization: cluster-apps];\n  id2\u003eKustomization: cluster-apps] --\u003e|Creates| id3\u003eKustomization: cloudnative-pg];\n  id2\u003eKustomization: cluster-apps] --\u003e|Creates| id5\u003eKustomization: postgres-cluster]\n  id2\u003eKustomization: cluster-apps] --\u003e|Creates| id8\u003eKustomization: atuin]\n  id3\u003eKustomization: cloudnative-pg] --\u003e|Creates| id4[HelmRelease: cloudnative-pg];\n  id5\u003eKustomization: postgres-cluster] --\u003e|Depends on| id3\u003eKustomization: cloudnative-pg];\n  id5\u003eKustomization: postgres-cluster] --\u003e|Creates| id10[Postgres Cluster];\n  id8\u003eKustomization: atuin] --\u003e|Creates| id9(HelmRelease: atuin);\n  id8\u003eKustomization: atuin] --\u003e|Depends on| id5\u003eKustomization: postgres-cluster];\n```\n\n---\n\n## \u003cimg src=\"https://fonts.gstatic.com/s/e/notoemoji/latest/1f30e/512.gif\" alt=\"🌎\" width=\"20\" height=\"20\"\u003e Networking \u0026 DNS\n\n\u003cdetails\u003e\n  \u003csummary\u003eClick to see a high-level phsyical network diagram\u003c/summary\u003e\n\n  \u003cimg src=\"docs/assets/network-diagram.excalidraw.svg\" align=\"center\" width=\"600px\" alt=\"network\"/\u003e\n\u003c/details\u003e\n\nApps hosted on my cluster are exposed using any combination of three different methods, depending on their use-case, security requirements, and intended audience. All three methods utilise fully encrypted HTTPS connections – TLS certificates are automatically provisioned and renewed by [Cert Manager](https://cert-manager.io) for each application.\n\n### Local Network\n\nThe first and easiest way that an app can be exposed is strictly on my local network. This is most often used for apps and services that have to do with home automation – given that every smart home device is on my local network, there is no need to expose e.g. a supporting service like MQTT any further than that.\n\nLocal deployments are accomplished by creating an Ingress of type `internal`, which will register a virtual IP for the service in a designated subnet (advertised via BGP) and provision a DNS record on the router with  the [ExternalDNS webhook provider for UniFi](https://github.com/kashalls/external-dns-unifi-webhook).\n\n### Privately Exposed (Tailscale)\n\nThe second and most common way that an app can be exposed is via [Tailscale](https://tailscale.com/kb/1236/kubernetes-operator). Creating an Ingress with the `tailscale` class will expose the application to my Tailnet, and [automagically](https://tailscale.com/kb/1081/magicdns) configure DNS records. Most self-hosted apps and dashboards are exposed using this Ingress class, so that they are accessible on my personal devices at a consistent URL no matter if I'm at home or abroad.\n\nTailscale also serves as a Kubernetes auth proxy, which I use in conjunction with the [Nautik](https://nautik.io/) iOS app to monitor and administer my Kubernetes cluster on-the-go.\n\n### Publicly Exposed\n\nThe final and least common way to expose an app is via `cloudflared`, the [Cloudflare Tunnel](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/) daemon. By routing all external traffic through Cloudflare's infrastructure, I gain the benefits of their global security infrastructure (notably DDoS protection). This is generally used for webhook endpoints which require access from the wider Internet, though I do expose a select few apps for friends and family.\n\nCreating an `external` Ingress will trigger using [ExternalDNS](https://github.com/kubernetes-sigs/external-dns) to provision a CNAME DNS record on Cloudflare which points at the Cloudflare Tunnel endpoint. The tunnel routes traffic securely into my cluster, where the ingress controller further routes it to the destination Service.\n\n---\n\n## \u003cimg src=\"https://fonts.gstatic.com/s/e/notoemoji/latest/1f48e/512.gif\" alt=\"☁️\" width=\"20\" height=\"20\"\u003e Cloud Dependencies\n\nWhile most of my infrastructure and workloads are self-hosted, I do rely upon the cloud for certain key parts of my setup. This saves me from having to worry about three things:\n1. Dealing with chicken/egg scenarios\n2. Critical services that need to be accessible, whether my cluster is online or not.\n3. The \"hit by a bus\" scenario - what happens to critical apps (e.g. Email, Password Manager, Photos, etc.) that my friends and family rely on when I'm no longer around.\n\nAlternative solutions to the first two of these problems would be to host a Kubernetes cluster in the cloud and deploy applications like [Vault](https://www.vaultproject.io/), [Vaultwarden](https://github.com/dani-garcia/vaultwarden), [ntfy](https://ntfy.sh/), and [Gatus](https://gatus.io/); however, maintaining another cluster and monitoring another group of workloads would frankly be more time and effort than I am willing to put in. (and would probably cost more or equal out to the same costs as described below)\n\n| Service                                     | Use                                                               | Cost           |\n|---------------------------------------------|-------------------------------------------------------------------|----------------|\n| [1Password](https://1password.com/)         | Secrets with [External Secrets](https://external-secrets.io/)     | ~$36/yr        |\n| [Cloudflare](https://www.cloudflare.com/)   | Domain/DNS                                                        | ~$24/yr        |\n| [Backblaze](https://www.backblaze.com)      | S3-compatible object storage                                      | ~$36/yr        |\n| [GitHub](https://github.com/)               | Hosting this repository and continuous integration/deployments    | Free           |\n| [Pushover](https://pushover.net/)           | Kubernetes Alerts and application notifications                   | $5 OTP         |\n| [UptimeRobot](https://uptimerobot.com/)     | Monitoring internet connectivity and external facing applications | Free           |\n| [Healthchecks.io](https://healthchecks.io/) | Dead man's switch for monitoring cron jobs                        | Free           |\n|                                             |                                                                   | Total: ~$10/mo |\n\n---\n\n## \u003cimg src=\"https://fonts.gstatic.com/s/e/notoemoji/latest/2699_fe0f/512.gif\" alt=\"⚙\" width=\"20\" height=\"20\"\u003e Hardware\n\n\u003cdetails\u003e\n  \u003csummary\u003eClick to see my rack\u003c/summary\u003e\n\n  ![1B51EA7B-3517-4614-B7FC-A15943763705_1_105_c](https://github.com/user-attachments/assets/dd9a2259-7ff3-42d4-a420-2711557483eb)\n\u003c/details\u003e\n\n\n| Device                      | Count | OS Disk     | Data Disk                                                              | RAM  | OS          | Purpose                    |\n|-----------------------------|-------|-------------|------------------------------------------------------------------------|------|-------------|----------------------------|\n| MS-01 (i9-12900H)           | 3     | 1TB M.2 SSD | 2TB M.2 SSD (Rook)                                                     | 96GB | Talos Linux | Kubernetes (control plane) |\n| Custom build ([link][pcpp]) | 1     | 1TB M.2 SSD | 4TB M.2 SSD                                                            | 96GB | Talos Linux | Kubernetes (gpu workloads) |\n| Synology DS918+             | 1     | -           | 2x14TB\u0026nbsp;HDD + 2x18TB\u0026nbsp;HDD + 2x1TB\u0026nbsp;SSD\u0026nbsp;R/W\u0026nbsp;Cache | 16GB | DSM 7       | NAS/NFS/Backup             |\n| JetKVM                      | 2     | -           | -                                                                      | -    | -           | KVM                        |\n| Home Assistant Yellow       | 1     | 8GB eMMC    | 1TB M.2 SSD                                                            | 4GB  | HAOS        | Home Automation            |\n| UniFi UDM Pro               | 1     | -           | -                                                                      | -    | UniFi OS    | Router                     |\n| UniFi USW Pro 24 PoE        | 1     | -           | -                                                                      | -    | UniFi OS    | Core Switch                |\n| Unifi USP PDU Pro           | 1     | -           | -                                                                      | -    | UniFi OS    | PDU                        |\n| CyberPower OR500LCDRM1U     | 1     | -           | -                                                                      | -    | -           | UPS                        |\n\n---\n\n## \u003cimg src=\"https://fonts.gstatic.com/s/e/notoemoji/latest/1f64f/512.gif\" alt=\"🙏\" width=\"20\" height=\"20\"\u003e Gratitude and Thanks\n\nHuge thank-you to the folks over at the [Home Operations](https://github.com/home-operations) community, especially [@onedrop](https://github.com/onedr0p), [@bjw-s](https://github.com/bjw-s), and [@buroa](https://github.com/buroa) – their home-ops repos have been an amazing resource to draw upon.\n\nBe sure to check out [kubesearch.dev](https://kubesearch.dev) for further ideas and reference for deploying applications on Kubernetes.\n\n---\n\n## \u003cimg src=\"https://fonts.gstatic.com/s/e/notoemoji/latest/1f6a7/512.gif\" alt=\"🚧\" width=\"20\" height=\"20\"\u003e Changelog\n\nSee the latest [release](https://github.com/zebernst/homelab/releases/latest) notes.\n\n---\n\n## \u003cimg src=\"https://fonts.gstatic.com/s/e/notoemoji/latest/2696_fe0f/512.gif\" alt=\"⚖\" width=\"20\" height=\"20\"\u003e License\n\nSee [LICENSE](./LICENSE).\n\n[pcpp]: https://pcpartpicker.com/b/qLrD4D\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzebernst%2Fhomelab","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fzebernst%2Fhomelab","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzebernst%2Fhomelab/lists"}