Self-hosted private cloud on bare metal. Talos + Cilium + ArgoCD + Longhorn + KubeVirt.
Two nodes today (1 control plane with scheduling on, 1 worker), designed to scale to a 3-node HA control plane.
| Path | Purpose |
|---|---|
cluster/ |
Talos machine configs + imperative bootstrap (Cilium, ArgoCD). Start at cluster/README.md. |
gitops/ |
ArgoCD's source of truth - root, infrastructure, operators, security, services, apps. |
manifests/ |
Ad-hoc / one-shot manifests, applied manually. Not reconciled by ArgoCD. |
scripts/ |
validate.sh and cluster.sh (restore, shutdown, start), behind the mise tasks. |
network/netboot/ |
netboot.xyz + ProxyDHCP install notes for the planned provisioning host. Not deployed. |
renovate.json5 |
Renovate config; runs every 4h via .github/workflows/renovate.yml. |
Conventions for agents and humans: CLAUDE.md.
brew install mise op
mise install # everything pinned in .mise.tomlop (1Password CLI) is the only required tool not managed by mise. It backs the op://homecloud/... URIs used in .env generation and in re-encrypting the bootstrap credential.
Common tasks live in .mise.toml - mise tasks lists them (render, validate, bootstrap, talos:upgrade, talos:upgrade-k8s, restore, cluster:shutdown, cluster:start, argo:sync). PRs touching gitops/ run mise run validate in .github/workflows/validate.yml.
Versions live next to the manifests - gitops/*/*/kustomization.yaml for chart versions, cluster/bootstrap/helmfile.yaml for Cilium / Gateway API / ArgoCD.
| Layer | Deployed |
|---|---|
infrastructure/ wave 0 |
cert-manager, external-dns, external-secrets, gateway, headlamp, infra-app-httproutes, keda, kube-prometheus-stack, loki, longhorn, metrics-server, reloader, secrets |
operators/ wave 5 |
cnpg, falco, kubevirt, mariadb, tailscale |
security/ wave 10 |
falco, trivy |
services/ wave 15 |
kubevirt (KubeVirt + CDI CRs) |
apps/ wave 100 |
actual-budget, authentik, cloudflared, gatus, mealie, media-stack, n8n, portfolio |
gitops/experimental/ is a staging area - no ApplicationSet reads it, so nothing in it runs. It mirrors the layers: apps/ holds homarr, outline, speedtest-tracker, uptime-kuma and the restore-test and pitr-test drill apps; infrastructure/ rancher; operators/ and services/ seaweedfs; security/ kubescape.
- Talos cluster - 1 control plane (scheduling on) + 1 worker.
- Infrastructure:
- Cilium CNI with eBPF datapath (no kube-proxy).
- Cilium Gateway API -
internalandexternalGateways, L2-announced on the LAN. - ArgoCD for GitOps.
- External Secrets + 1Password as the runtime secret path.
- external-dns for dynamic DNS records via Cloudflare.
- cert-manager for TLS certificates (DNS-01).
- Longhorn for replicated block storage.
- kube-prometheus-stack for metrics, alerting and Grafana.
- Grafana Loki + Alloy for log aggregation.
- Headlamp in-cluster.
- KubeVirt + CDI for VMs on Kubernetes.
- CNPG for Postgres, mariadb-operator for MariaDB.
- Tailscale operator for remote access.
- Renovate for chart and image versions.
- SOPS + age for the bootstrap 1Password credential (not wired into ArgoCD). See cluster/docs/argocd.md.
- Applications:
- Media stack - Jellyfin, Seerr, Radarr, Sonarr, Bazarr, Prowlarr, qBittorrent behind Gluetun.
- n8n for workflow automation.
- Authentik for SSO.
- Mealie recipes, Actual Budget, Gatus status page on
status.nulcell.com(replaced Uptime Kuma, parked ingitops/experimental/). - Cloudflare Tunnel publishing internal apps to the internet.
- Portfolio website - Astro + nginx on
nulcell.com.
- Serverless and Messaging:
- RabbitMQ Operator for messaging.
- Knative Operators for serverless workloads.
- Serving for request-driven autoscaling.
- Eventing for event-driven architecture.
- RabbitMQ plugin for messaging events.
- Security tooling:
- Falco (modern eBPF) + Falcosidekick + Falco Talon for runtime detection and automated response.
- Trivy Operator for continuous vulnerability and config scanning, with the Headlamp plugin.
- Kyverno for policy enforcement and configuration validation. See gitops/security/README.md.
- Policy Reporter for aggregating
PolicyReportCRDs.
- Provisioning:
- helmfile bootstrap (
cluster/bootstrap/helmfile.yaml). - Terraform + Terragrunt for Talos + bootstrap - plan in cluster/docs/terraform.md.
- Pi-hole (DNS + DHCP) + netboot + Tailscale on a Raspberry Pi for bare-metal provisioning.
- helmfile bootstrap (
- Testing:
- Kube-monkey for chaos testing.
- Backups (runbook):
- Longhorn volume backups to AWS S3 (opt-in per PVC).
- CNPG barman-cloud backups to S3 for gatus, mealie, n8n and authentik.
- Point-in-time restore drill, rehearsed with
gitops/experimental/apps/pitr-test(steps in the runbook).
- HA home cluster.
- 2.5GbE network upgrade for cluster nodes.
- Dedicated control-plane nodes with similar mini-pcs (1 -> 3, never 2).
- 2-3 additional workers with other hardware.
flowchart TD
classDef external fill:#f9f,stroke:#333,stroke-width:2px;
classDef gitops fill:#bbf,stroke:#333,stroke-width:2px;
classDef routing fill:#bfb,stroke:#333,stroke-width:1px;
classDef compute fill:#ffb,stroke:#333,stroke-width:1px;
classDef storage fill:#fbb,stroke:#333,stroke-width:1px;
classDef security fill:#f99,stroke:#333,stroke-width:1px;
classDef obs fill:#dfd,stroke:#333,stroke-width:1px;
subgraph Ext ["External / Cloud"]
GH[GitHub repo - nulcell/homecloud]:::external
CF[Cloudflare DNS + Tunnel]:::external
OP[1Password vault]:::external
TS[Tailscale]:::external
S3[AWS S3 - backups]:::external
end
subgraph GitOps ["GitOps"]
Argo[ArgoCD]:::gitops
ESO[External Secrets Operator]:::gitops
GH -->|Sync manifests| Argo
OP -->|ClusterSecretStore| ESO
ESO -->|Materialises Secrets| Argo
end
subgraph Networking ["Networking & Ingress"]
GWi[Gateway 'internal' - 10.10.20.2]:::routing
GWe[Gateway 'external' - 10.10.20.6]:::routing
Cilium[Cilium CNI - eBPF, kube-proxy replacement, L2 announcements]:::routing
ExtDNS[external-dns]:::routing
CertMan[cert-manager]:::routing
CFD[cloudflared DaemonSet]:::routing
CF -->|Tunnel| CFD
CFD -->|Origin request| GWi
TS -->|Tailnet| Cilium
GWi --> Cilium
GWe --> Cilium
GWi -.->|HTTPRoute hostnames| ExtDNS
GWe -.->|HTTPRoute hostnames| ExtDNS
ExtDNS --> CF
CertMan -.->|wildcard-nulcell-tls via DNS-01| GWi
CertMan -.-> GWe
end
subgraph DataStore ["Storage & Databases"]
LH[Longhorn block storage]:::storage
CNPG[CNPG operator]:::storage
PGDB[Postgres - authentik, gatus, mealie, n8n]:::storage
MDB[mariadb-operator]:::storage
CNPG -->|Manages| PGDB
PGDB -->|Claims PVs| LH
LH -->|Volume backups| S3
PGDB -->|Base backups + WAL| S3
end
subgraph Compute ["Compute & Workloads"]
KVirt[KubeVirt + CDI]:::compute
subgraph Apps ["Applications"]
Media[media-stack - Jellyfin, arr apps, Gluetun]:::compute
N8N[n8n]:::compute
Auth[authentik]:::compute
Misc[mealie, actual-budget, gatus, portfolio]:::compute
end
Cilium --> Apps
Cilium --> KVirt
Apps -->|Secrets| ESO
Apps -->|Persistent storage| LH
Apps -->|Database| DataStore
KVirt -->|Claims PVs| LH
end
subgraph SecLayer ["Security"]
Falco[Falco - modern eBPF + k8saudit]:::security
FB[Fluent Bit - kube-apiserver audit logs]:::security
FSide[Falcosidekick + UI]:::security
Talon[Falco Talon - response actions]:::security
Trivy[Trivy Operator - vulnerability and config scans]:::security
FB -->|Audit webhook| Falco
Falco -->|Alerts| FSide
FSide -->|priority >= error| Talon
end
subgraph Observability ["Observability"]
KPM[kube-prometheus-stack]:::obs
Loki[Grafana Loki]:::obs
Alloy[Grafana Alloy]:::obs
Grafana[Grafana]:::obs
Compute -->|Metrics| KPM
Compute -->|Pod logs| Alloy
Alloy --> Loki
Loki -.->|Datasource| Grafana
KPM --> Grafana
FSide -->|Alerts >= error| KPM
end
Trivy -->|ServiceMonitor| KPM
Argo -.->|Deploys & manages| Networking
Argo -.->|Deploys & manages| DataStore
Argo -.->|Deploys & manages| Compute
Argo -.->|Deploys & manages| SecLayer
Argo -.->|Deploys & manages| Observability
Detection and response run today; posture scanning is new and Falco will be tuned for its noise. Kyverno (policy) is planned.
flowchart LR
Sys[Syscalls - modern eBPF driver]
Audit[kube-apiserver audit log]
FB{{Fluent Bit}}
Audit -->|Talos audit log tail| FB
FB -->|POST :9765/k8s-audit| Falco
subgraph Falco ["Falco DaemonSet"]
Rules[Rulesets - falco-rules, incubating, k8saudit]
Plugins[Plugins - container, k8smeta, k8saudit, json]
end
Sys --> Falco
Falco -->|JSON http_output :2801| Sidekick[Falcosidekick]
Sidekick -->|WEBUI| UI[Falcosidekick UI + redis-stack]
Sidekick -->|priority >= error| Talon[Falco Talon]
Sidekick -->|priority >= error| AM[Alertmanager - kube-prometheus-stack]
Talon -->|k8sevents notifier| Events[Kubernetes Events]
subgraph Posture ["Posture scanning"]
Trivy[Trivy Operator]
TServer[Trivy server - vulnerability DB]
Reports[(Report CRDs - vulnerabilities, config audit, secrets, RBAC, compliance)]
Trivy -->|scan jobs| TServer
Trivy --> Reports
end
Reports --> HL[Headlamp Trivy plugin]
Trivy -->|ServiceMonitor| Prom[Prometheus]
Kyverno[Kyverno - planned]:::planned
Kyverno -.->|PolicyReports| PR[Policy Reporter - planned]:::planned
classDef planned stroke-dasharray: 5 5;
flowchart TB
subgraph Public ["Public Internet"]
direction LR
PubUser[Users / Clients]
PrivUser[Admin / Operator]
CFD[Cloudflare DNS]
CFT[Cloudflare Tunnel]
TS[Tailscale ZTNA]
PubUser -->|DNS| CFD
PubUser -->|HTTPS| CFT
PrivUser -->|ZTNA| TS
end
subgraph Network ["Home Network - 10.10.16.0/20"]
direction TB
Router[Main router - 10.10.31.254, DNS]
Switch[LAN switch]
Router --> Switch
subgraph Cluster ["HomeCloud Kubernetes Cluster"]
direction LR
K8sVIP[API VIP - k8s.nulcell.com / 10.10.25.25]
CP1[talos-tgu-9aw - control plane, 10.10.17.5]
W1[talos-ztk-5fl - worker, 10.10.27.254]
LB[Cilium L2 LB pool - 10.10.20.0-254]
K8sVIP <-->|VIP| CP1
W1 -->|API| K8sVIP
CP1 --- LB
W1 --- LB
end
Switch <-->|LAN| Cluster
end
Router -->|WAN| Public
CFT -->|cloudflared to internal Gateway| Cluster
TS -->|ZTNA| Cluster