The cluster uses an app-of-apps pattern: a single root ArgoCD Application points to the k8s-cluster-config Git repository. Inside that repo, each component has its own application.yaml. ArgoCD reads these and creates child Applications — one per component.
Why app-of-apps? Each component is independently managed. You can sync, roll back, or pause a single component without touching others. ArgoCD tracks drift — if someone manually edits a K8s resource, ArgoCD detects the diff and can auto-heal it.
ArgoCD uses argocd.argoproj.io/sync-wave annotations to control deployment order. Lower waves deploy first and must become healthy before the next wave starts.
Why this order? Each layer depends on the one before:
Every app that needs a secret follows the same pipeline:
kv/authentik/postgresThis means: to add a new secret to a pod, you never edit the pod spec directly. You write the value to Vault, create a VaultStaticSecret CR, and the secret appears automatically.
All components are deployed via ArgoCD. Lower sync waves deploy first.
| Wave | Component | What It Does | Helm Chart | Version | Namespace |
|---|---|---|---|---|---|
| -10 | coredns-custom | Custom CoreDNS config (local DNS overrides for homelab domains) | (raw manifests) | — | kube-system |
| -5 | cilium | CNI + eBPF networking; replaces Flannel + kube-proxy; provides NetworkPolicy, Hubble observability, transparent encryption | cilium |
1.19.2 | kube-system |
| 0 | cert-manager | Automatically issues and renews TLS certificates (Let's Encrypt via ACME DNS challenge) | cert-manager |
1.20.1 | cert-manager |
| 0 | gateway-api | Gateway API CRDs (GatewayClass, Gateway, HTTPRoute, GRPCRoute) required by Istio | (raw manifests) | — | — |
| 1 | coredns-patch | Patches the Talos-bootstrapped CoreDNS Deployment (replica count, anti-affinity) | (raw manifests) | — | kube-system |
| 1 | istio-base | Istio CRDs and base components (ValidatingWebhookConfiguration) | base |
1.24.3 | istio-system |
| 1 | metallb | Assigns real IP addresses (192.168.88.12) to LoadBalancer Services via L2 mode | metallb |
0.15.3 | metallb-system |
| 2 | istio-istiod | Istio control plane: mutual TLS, traffic management, Envoy sidecar injection | istiod |
1.24.3 | istio-system |
| 2 | node-recovery | Removes node.kubernetes.io/unschedulable taint when a node rejoins after maintenance |
(raw manifests) | — | kube-system |
| 3 | crowdsec | IDS/IPS and WAF: IP reputation (LAPI), per-request deep inspection (AppSec), community blocklist (CAPI); integrates with Istio via Envoy ext_authz | crowdsec |
0.22.1 | crowdsec |
| 3 | istio-gateway | Istio ingress Gateway (homelab-gateway): terminates HTTPS, routes via HTTPRoute; replaces Traefik |
gateway |
1.24.3 | istio-system |
| 3 | longhorn | Distributed block storage using K8s node SSDs; provides longhorn StorageClass (2-replica default) |
longhorn |
1.11.1 | longhorn-system |
| 3 | nfs-synology | NFS provisioner for Synology HDD pool; provides nfs-synology StorageClass |
nfs-subdir-external-provisioner |
4.0.18 | nfs-storage |
| 3 | nfs-subdir-retain | NFS provisioner for TrueNAS NVMe (retain policy) | nfs-subdir-external-provisioner |
4.0.18 | nfs-storage |
| 3 | nfs-subdir-delete | NFS provisioner for TrueNAS NVMe (delete policy — ephemeral) | nfs-subdir-external-provisioner |
4.0.18 | nfs-storage |
| 3 | metrics-server | Provides kubectl top and HPA with CPU/memory usage data |
metrics-server |
3.13.0 | kube-system |
| 4 | falco | Container runtime security: detects privilege escalation, container escapes, suspicious syscalls via eBPF kernel probes | falco |
8.0.1 | falco |
| 4 | vault | Secret store; single source of truth for all passwords and tokens (3-node HA, Raft backend) | vault |
0.32.0 | vault |
| 5 | argocd-oidc | Configures ArgoCD to authenticate via Authentik SSO (HTTPRoute + VaultStaticSecret) | (raw manifests) | — | argocd |
| 5 | descheduler | Evicts imbalanced or mis-placed pods so the scheduler can re-place them optimally | descheduler |
0.35.1 | kube-system |
| 5 | vso | Watches VaultStaticSecret CRs and syncs Vault values into K8s Secrets | vault-secrets-operator |
1.3.0 | vso-system |
| 6 | technitium | Primary + secondary DNS servers with ad blocking, DoT/DoH, DNSSEC; serves cluster and home network | (raw manifests) | — | technitium |
| 6 | velero | K8s backup: manifests + NFS PVC data via Kopia fs-backup to MinIO | velero |
12.0.0 | velero |
| 7 | external-dns | Watches HTTPRoute/Gateway objects and automatically creates Technitium DNS entries via rfc2136+TSIG | external-dns |
1.20.0 | external-dns |
| 7 | suricata | Network IDS: deep packet inspection on cluster traffic, intrusion signatures | (raw manifests) | — | suricata |
| 8 | harbor | Private container registry with vulnerability scanning (Trivy) and image signing (Cosign) | harbor |
1.18.3 | harbor |
| 8 | omada | TP-Link Omada network controller for managing home Wi-Fi APs | (raw manifests) | — | omada |
| 8 | syncthing | P2P file sync: K8s pod reads TrueNAS NFS (Send Only) → Synology NAS (Receive Only). Replicates Proxmox VM backups to Synology archive | (raw manifests) | — | syncthing |
| 10 | authentik | Identity Provider (IdP) and SSO gateway; all external apps authenticate through it via OIDC | authentik |
2026.2.1 | authentik |
| 11 | gitlab-runner | GitLab CI/CD runner for in-cluster pipeline jobs (Kaniko builds, Trivy scans, manifest updates) | gitlab-runner |
0.87.0 | gitlab-runner |
| 11 | grafana | Dashboards UI for metrics, logs, and traces; authenticated via Authentik OIDC | grafana |
10.5.15 | monitoring |
| 11 | otel-collector | OTLP ingestion hub; routes traces → Tempo, metrics → VictoriaMetrics, logs → VictoriaLogs | opentelemetry-collector |
0.147.1 | monitoring |
| 11 | tempo | Distributed trace storage (Grafana Tempo); stores 14 days of traces in MinIO | tempo |
1.24.4 | monitoring |
| 11 | vector | Log collector DaemonSet; ships K8s pod logs and MikroTik syslog to VictoriaLogs | vector |
0.51.0 | monitoring |
| 11 | victoria-logs | Log storage backend; stores 30 days of pod stdout and MikroTik syslog | victoria-logs-single |
0.11.30 | monitoring |
| 11 | victoria-metrics | Time-series database (Prometheus-compatible); stores 90 days of metrics | victoria-metrics-single |
0.34.0 | monitoring |
| 11 | vmagent | Metrics scraper DaemonSet; collects from K8s nodes, NAS, Proxmox, network gear | victoria-metrics-agent |
0.35.0 | monitoring |
| 11 | vmalert | Evaluates alerting rules against VictoriaMetrics; sends firing alerts to Alertmanager → ntfy | victoria-metrics-alert |
0.36.0 | monitoring |
| 12 | ente-auth | Self-hosted 2FA TOTP server (Ente Auth museum backend) | (raw manifests) | — | ente-auth |
| 12 | kube-state-metrics | Exposes K8s object state (pod health, PVC status, deployment replicas) as metrics | kube-state-metrics |
7.2.2 | monitoring |
| 13 | nas-ingress | HTTPRoutes for NAS Docker apps (port-forwards traffic to 192.168.88.19) | (raw manifests) | — | nas-ingress |
| 14 | nextcloud | Self-hosted file sync and collaboration (Dropbox/Google Drive alternative) | nextcloud |
9.0.4 | nextcloud |
| 15 | vault-ui | Vault web UI (Headlamp-based), LAN-only access | (raw manifests) | — | vault |
These components have resources/ directories in k8s-cluster-config containing VSO manifests (ServiceAccount + VaultAuth + VaultStaticSecret).
| Component | Namespace | Vault Path | K8s Secret Created |
|---|---|---|---|
authentik |
authentik | kv/authentik/* |
authentik-secret-key, authentik-db, etc. |
argocd-oidc |
argocd | kv/authentik/argocd-oidc |
argocd-oidc-secret |
grafana |
monitoring | kv/monitoring/grafana/* |
grafana-admin, grafana-oidc |
harbor |
harbor | kv/authentik/harbor-oidc |
harbor-oidc-secret |
nextcloud |
nextcloud | kv/nextcloud/admin |
nextcloud-admin |
technitium |
technitium | kv/technitium/admin |
technitium-admin |
external-dns |
external-dns | kv/external-dns/tsig |
technitium-tsig |
vmalert |
monitoring | kv/monitoring/alertmanager/ntfy |
alertmanager-ntfy |
velero |
velero | kv/velero/minio |
velero-minio-creds |
crowdsec (bouncer key) |
crowdsec | kv/crowdsec/bouncer |
crowdsec-bouncer-key |
crowdsec (ntfy token) |
crowdsec | kv/crowdsec/ntfy |
crowdsec-ntfy |
tempo |
monitoring | kv/monitoring/tempo/minio |
tempo-minio-creds |
vmalert (restore-test) |
monitoring | kv/monitoring/db-backup-minio |
restore-test-minio |
Every namespace that hosts a workload has a CiliumNetworkPolicy (CNP) — default-deny ingress + scoped egress. The cluster moved from no policies → near-complete coverage in two passes (2026-04-20 and 2026-04-27/28). Hubble flow logs at hubble.homelab.vyanh.uk are the easiest way to debug a Policy denied DROPPED.
| Namespace | What it allows in | What it allows out | Notes |
|---|---|---|---|
authentik |
Istio gateway → 9000/9443; kubelet probes | DNS, Vault, Postgres, OAuth callbacks | OIDC IdP — heavy ingress |
nextcloud |
Istio gateway → 80; kubelet probes | DNS, Postgres, NAS NFS | |
technitium |
LAN → 53/853 (DNS), Istio gateway → 5380 (UI) | DNS upstream (DoH 853 → CF/Google) | Plus port 5380 for the configure-job |
cert-manager |
Self + Prometheus scrape :9402 | DNS, Let's Encrypt 443, Cloudflare API | |
vault |
Istio gateway → 8200 (proxy), peers → 8201 | DNS, K8s API (auto-unseal), peer Vaults | Allow istio-system ingress for the public proxy route |
vault-ui |
Istio gateway → 80 | DNS, Vault | |
vso-system |
Self + cert-manager webhook | DNS, Vault, K8s API | Watches VSS CRs cluster-wide |
external-dns |
Self + Prometheus scrape | DNS, Technitium ClusterIP :53 (RFC2136 TSIG) | |
omada |
LAN → 8043 (UI), AP discovery on the broadcast subnet | DNS, MongoDB sidecar | |
gitlab-runner |
— | DNS, GitLab :443/2222, Harbor :443/8080, Vault :8200, MinIO :9000, OTel Collector :4317 | Manager pod tight; job pods need broader egress (Kaniko/MinIO/Harbor) |
monitoring |
Istio gateway → vmselect/vmalert/Grafana; vmagent → :9402; vmalert → blackbox; istio-system → VictoriaLogs/VictoriaMetrics | DNS, MinIO (Velero/Tempo), all scrape targets, Alertmanager → ntfy bridge | kubelet probe rule required everywhere — see below |
harbor |
Istio gateway → 80; intra-namespace registry/core/redis | DNS, Postgres, MinIO, Authentik OIDC (via istio-system) | |
life-ops |
Istio gateway → backend/frontend; LifeOps backend → OTel Collector :4317 | DNS, Postgres, OTel Collector :4317 | |
argocd |
Istio gateway → server :443; kubelet probes :8083/:9001 | DNS, GitLab, Helm registries (incl. Harbor cache), K8s API | |
velero |
Self + Prometheus scrape | DNS, MinIO :9000, K8s API | |
kyverno |
Self + admission webhook | DNS, K8s API | Cleanup CronJobs disabled (see Kyverno gotchas) |
crowdsec |
LAPI :8080 within ns + Envoy bouncer ingress; AppSec :7422 | DNS, K8s API for hub init, world :443 | Bouncer egress allowed to LAPI across namespaces |
syncthing |
LAN → 22000 (sync), Istio gateway → 8384 (UI) | DNS, GitHub for relay discovery |
Cilium with enable-endpoint-routes: true + native routing makes kubelet probes from other nodes arrive with the reserved:remote-node identity, not reserved:host. Every CNP that wants liveness / readiness probes to succeed needs both:
ingress:
- fromEntities:
- host
- remote-node
toPorts:
- ports:
- port: "<probe-port>"
protocol: TCP
Using only host works for in-place probes but silently drops cross-node probes → liveness failures the first time the pod is rescheduled to a different node from kubelet. This was retro-applied across every monitoring CNP on 2026-04-25 / 2026-04-27.
netpol.yaml (or <name>-cnp.yaml) into the component's resources/ directory.host + remote-node probe rule for the container's probe port.hubble observe --from-pod <ns>/<pod> --verdict DROPPED after deploy, then iterate.