GitOps
Argo CDPulls from Git and reconciles the platform and workloadsPlatform capabilities
- Networking
- Cilium · Gateway API
- Storage
- Longhorn · NFS
- Secrets
- External Secrets
- Identity
- Pocket ID · oauth2-proxy
- Policy
- Kyverno · Trivy
- Observability
- Prometheus · Loki · Grafana
- Delivery
- Argo Rollouts · Renovate
- Backups
- Longhorn · etcd to NAS
Workloads
- Home Assistant
- Zigbee2MQTT
- Media stack
- BookOrbit
- Obsidian LiveSync
Overview
I run this homelab as a four-node Raspberry Pi 5 Kubernetes platform built on Talos Linux. Argo CD reconciles the cluster from a public Git repository, giving me a realistic place to operate the same platform components together rather than testing each one in isolation.
It is production-inspired, but it is not production infrastructure. It is a learning and experimentation platform that I can take apart, rebuild and change as the repository evolves.
Motivation
I wanted an environment where I controlled the hardware, operating system and Kubernetes configuration. Running the platform exposes details that a diagram does not: reconciliation order, storage behaviour, secret delivery, networking failures and the work involved in upgrading components without losing sight of the whole cluster.
Goals
- Keep desired state reviewable in Git, and check it before it reaches the cluster.
- Make routine changes reproducible rather than dependent on manual cluster edits.
- Manage networking, secrets, identity, storage and observability as part of the platform.
- Make security rules explicit, with a written reason for every exception.
- Run useful workloads while preserving space for experiments.
- Document decisions and trade-offs, not only installation steps.
Architecture
Four Raspberry Pi 5 nodes provide the physical foundation: one control-plane node and three workers. The workers each have a separate NVMe drive for persistent data. Talos Linux supplies the API-managed operating system for Kubernetes.
On top of that, Cilium handles networking and ingress, Longhorn provides replicated storage, External Secrets retrieves credentials from 1Password, and Pocket ID handles sign-in. Kyverno and Trivy check what runs in the cluster, and Prometheus, Loki and Grafana show what it is doing.
Platform services and applications live in separate Argo CD projects. The platform project may install cluster-wide resources, while the apps project can only touch its own namespaces. Home Assistant and the other self-hosted applications sit above the platform components they depend on instead of being mixed into one set of manifests.
GitOps model
Argo CD compares the public repository with the cluster and reconciles the declared state. I use an app-of-apps structure to bootstrap and compose the platform, with Helm and Kustomize handling package installation and configuration.
Every pull request and push runs a GitHub Actions workflow that renders each Application the way Argo CD would, validates the result against the Kubernetes schemas, runs the policy tests and scans new commits for leaked secrets. Renovate runs inside the cluster and opens pull requests for chart, image and CI tool updates, so upgrades go through the same review path as my own changes. Argo CD sends failed syncs and degraded applications to ntfy, so problems reach me as notifications.
I chose this model so the path from a repository change to a running component remains visible. It also means ordering and dependencies need attention: a broken declaration can be applied just as reliably as a correct one.
Networking and ingress
Cilium provides Kubernetes networking without kube-proxy, and Hubble shows the traffic between workloads. All web traffic enters through a single Cilium Gateway using the Gateway API. It terminates TLS with a wildcard certificate that cert-manager renews through Let’s Encrypt. MetalLB gives the Gateway its address on the home network, and Tailscale gives me remote access without opening ports.
I keep home-network addresses and internal names out of the public documentation because they add no value to understanding the design.
Identity and access
Pocket ID is the single sign-on provider, and I sign in with passkeys. Argo CD and Grafana use it directly; their local admin logins are turned off. Longhorn, Hubble and Policy Reporter have no login of their own, so each sits behind an oauth2-proxy instance, and a Cilium network policy makes sure nothing except that proxy can reach the UI.
A single identity provider is also a single point of failure, so the break-glass routes are written down: port-forwarding, Argo CD’s core mode and one-time login links.
Secrets management
I keep credentials outside Git. External Secrets retrieves values held in 1Password, while the application manifests describe which secret a workload needs without containing the value itself.
This keeps the repository useful and public without turning encrypted or encoded credentials into part of the project history. Secret delivery is still a dependency of the workloads, so a failed retrieval has to be visible during troubleshooting.
Policy and security
Every namespace enforces the baseline Pod Security Standard, and the few that need host access carry a reason in Git. On top of that, Kyverno checks each workload against the restricted standard and a few house rules: pinned image tags, resource requests, health probes and standard labels. Workloads that cannot comply, mostly storage and node agents, get a narrow exception that names the rule and explains why.
The policies currently run in audit mode. I will switch them to enforce one at a time once each has stayed clean, and each has tests that run in CI. Trivy Operator scans the running images for fixable high and critical vulnerabilities and alerts me when a new critical one appears.
Storage and backups
Longhorn provides replicated storage on the worker NVMe drives for workloads that cannot be recreated from configuration alone. Large media and book libraries stay on my NAS and are mounted over NFS, so they are not copied across the cluster.
Longhorn backs up every volume to the NAS each night. Because the cluster has a single etcd member, a nightly job also saves an etcd snapshot there, and the control-plane recovery procedure is documented. This is where the Raspberry Pi hardware is most visible as a constraint: the storage design and recovery options are not the same as those of a managed cloud service.
Observability
Prometheus collects metrics from the cluster, Cilium, Hubble and the applications. Grafana Alloy ships logs from every node to Loki. Alertmanager sends alerts to ntfy, and Grafana brings metrics and logs together in one place. I use that data to separate application behaviour from resource pressure, networking problems and node-level constraints when something does not behave as expected.
Resource requests are based on measured usage rather than guesses. When memory runs short, priority classes make the media services give way first, so Home Assistant and its Zigbee devices keep running.
Applications
The cluster runs the services I use at home:
- Home Assistant for climate control and garden irrigation, with Zigbee2MQTT and Mosquitto for the Zigbee devices.
- A media stack for finding and organising series and films, with the download client routed through a VPN.
- BookOrbit for my book library, with its own PostgreSQL database.
- A CouchDB backend that keeps my Obsidian notes in sync across devices.
They make the environment useful day to day while exercising the same GitOps, secrets, storage and networking paths as the rest of the platform. A small smoke-test application uses Argo Rollouts canary releases, which gives me a predictable workload for testing rollouts, metrics and alerts.
Operational considerations
The lab borrows production practices where they help me operate it: changes are declarative and validated, monitoring is part of the cluster, and recovery work is documented. The hardware and home setting still make it an experimentation environment rather than a production service.
The public portfolio website is intentionally hosted on Cloudflare Pages. A personal site should remain available independently of homelab maintenance, connectivity or experiments.
Decisions and trade-offs
I chose Talos Linux because its narrow, API-managed operating model fits a Kubernetes-only cluster. The trade-off is that familiar host-level administration is intentionally unavailable, so diagnosis and maintenance follow the Talos interfaces.
I run a single control-plane node so that three of the four nodes stay available for workloads and storage replicas. That makes the control plane a single point of failure, which is why the etcd snapshots and recovery steps matter.
GitOps gives the repository a clear role as desired state, but adds reconciliation behaviour that I have to account for during failures. App-of-apps keeps the bootstrap path visible, but only stays understandable when the repository structure remains disciplined.
I picked Pocket ID over Keycloak because it fits in a fraction of the memory. It runs as a single instance, so if it is down, sign-in fails until it recovers. Similarly, Kyverno is set up so that its own failure never blocks deployments while the policies are still in audit mode.
Raspberry Pi hardware keeps the cluster tangible and practical for experimentation. It also brings ARM compatibility, storage and resource constraints that would not shape every cloud or datacentre cluster in the same way.
Future improvements
I only want to add components when operating the cluster shows a real need for them. Next on the list:
- Move the Kyverno policies from audit to enforce, one at a time.
- Add default-deny network policies per namespace, based on the traffic Hubble has observed.
- Build, sign and attest my own container images in CI, and verify them in the cluster.
- Collect Kubernetes API audit logs and add runtime security monitoring.
- Practise restoring from backups instead of only taking them.
Repository
The public repository contains the declarative cluster configuration and supporting documentation. It is the best place to inspect the current implementation.