↓Skip to main content

POPIA, PCI-DSS, and My Proxmox Cluster: Bank-Grade Security at Home

·11 mins
Homelab Security Kubernetes Security Popia Pci-Dss Cilium Sealed-Secrets Keycloak Gitops Network-Segmentation Compliance
Chrisando Wagner
Author
Chrisando Wagner
25+ years across the full estate — Microsoft/VMware, OpenStack/Red Hat, cloud-native Kubernetes. These days that’s reference architectures, FinOps governance, and regulatory guardrails other teams build against. When I’m not debating gateway API controllers, I’m automating something in my homelab.

A recruiter once asked me, half-joking, whether I ran my home network “like a bank.” I laughed, said no, and then drove home and found I’d left a Grafana dashboard exposed on a LoadBalancer IP with no auth in front of it. That was years ago, before the current cluster existed, but the discomfort of that gap never quite left me. My day job is Cloud Architect at a banking group, where POPIA and PCI-DSS aren’t abstractions — they’re the reason certain designs get rejected in review before they ever reach a pull request. My homelab is not a bank. Nobody’s card data lives on it, no regulator will ever audit it, and I will say that plainly and repeatedly in this post so nobody mistakes what follows for a legal claim. What I’ve done instead is take the discipline those regulations instil — because I practise it forty hours a week whether I like it or not — and apply it to four nodes and a Mikrotik router in a room in Johannesburg.

This isn’t “how to comply with POPIA at home,” because POPIA doesn’t apply to a hobby cluster serving a personal blog. It’s “what does twenty-five years of being told ’no’ by security review teach you about running your own infrastructure well” — and the answer turns out to be more useful than I expected.


The gap between “it works” and “it’s defensible”
#

Most homelab content stops at “it works.” A service comes up, a green checkmark appears in ArgoCD, the blog post ends. Banking infrastructure has a second bar that has nothing to do with whether the thing runs: can you explain, after the fact, exactly who could have touched this, when, and why? That’s the muscle POPIA and PCI-DSS build in you, whether or not you agree with every clause of either.

POPIA (South Africa’s Protection of Personal Information Act) cares about where data lives, who processes it, and what happens the moment something goes wrong with it — data residency and breach notification, at the core. PCI-DSS cares about isolating anything that touches cardholder data behind strict network boundaries, encrypting it in transit and at rest, and logging access to it in a way that survives an auditor’s questions. Neither has any jurisdiction over my Proxmox cluster. But both are built around four questions that are just good engineering questions regardless of legal exposure:

  • Where does this data actually live, and do I know that for certain?
  • Who — or what service account — can reach this system, and why do they need to?
  • If this secret leaked tomorrow, would I know, and could I prove what it touched?
  • Can I reconstruct, from a log or a Git commit, exactly what changed and when?

I don’t run compliance scans against my homelab. I do ask myself those four questions before I ship anything to it, and the answers shape real design decisions — not just aspirations.


Data residency, homelab-style
#

Data sovereignty was the subject of my postgraduate research at Liverpool — who owns data, where it physically sits, and what obligations follow it across a border. In a bank, that’s about knowing which jurisdiction your S3 bucket or database replica sits in, because the answer determines which laws apply. At home, the equivalent question is smaller but not trivial: do I actually know where every piece of state in this cluster lives, and have I made that choice on purpose?

Concretely: my CloudNativePG cluster and MariaDB Galera nodes run on local storage across the four Proxmox nodes, not on some external volume I’ve lost track of. Sealed Secrets are encrypted with a key that never leaves the cluster’s controller — the ciphertext sitting in my Git repository is worthless without it, which means my “data residency boundary” for secrets is explicitly the cluster itself, not GitHub, not my laptop, not a password manager export I forgot to delete. When I decided to self-host Gitea rather than push everything to a public forge, that was the same instinct: know exactly which box your data sits on, and be able to say so without guessing.

None of this is regulatory compliance. It’s just refusing to let “where does this live” become a question I’d have to go and find out the answer to.


Network segmentation: Cilium policies instead of a flat LAN
#

PCI-DSS’s network segmentation requirement exists because a flat network means a compromise of the guest wifi is one hop from the payment processor. Homelabs are notorious for being exactly that flat network — one VLAN, everything trusts everything, and the “firewall” is whatever the router ships with by default.

My cluster doesn’t route that way. Cilium is deployed with kubeProxyReplacement: true and its own eBGP peering to the Mikrotik router (see Homelab Part 3 for the full peering setup), which means every Service gets a real, routable IP advertised over BGP rather than NodePort sprawl — but routability isn’t the same as reachability. I use CiliumNetworkPolicy to actually enforce which namespaces can talk to which: Keycloak needs to reach its Postgres backend and nothing else outward; the Argo Workflows controller needs registry and Git access but has no business talking to Vaultwarden; Hubble gives me the observability to prove that boundary is actually holding rather than assuming it is.

That last point matters more than the policy YAML itself. A segmentation rule you can’t verify is a rule you’re hoping about. Hubble’s flow visibility is the part of this that most homelab writeups skip — it’s easy to write a CiliumNetworkPolicy and never check whether traffic that should be dropped is actually being dropped. I’ve caught policies that looked correct on paper but were too permissive because a selector matched more pods than I intended. That’s precisely the kind of gap a PCI-DSS segmentation test is designed to surface in a bank’s network — a scoping exercise you can’t skip in a regulated environment. At home nobody makes me run that test. I run it anyway, because the point was never the audit, it was catching the mistake.


Least privilege: Keycloak OIDC instead of “everyone shares the admin password”
#

Access control is the single largest control family in PCI-DSS, and for good reason — most breaches aren’t clever exploits, they’re a credential that had more reach than anyone remembered granting it. The homelab-default failure mode is a shared admin password pasted into every service’s login screen, because setting up real identity federation for a cluster nobody else uses feels like overkill.

I decided it wasn’t. Keycloak runs as the OIDC provider for every service in the stack that supports SSO — Grafana, ArgoCD, Gitea, Vaultwarden’s admin panel, the Kargo UI. Every one of those authenticates against the same identity source, which means access can be revoked in one place instead of hunting through a dozen local user tables, and — just as importantly — I can actually answer “who has access to what” without opening each service in turn. That’s least privilege’s quieter cousin: not just restricting access, but being able to enumerate it on demand. A homelab where I have to log into six admin panels to answer “could this account see production” is a homelab I don’t actually understand.

The service accounts follow the same logic. The Argo Workflows service account that builds and pushes images has exactly the RBAC it needs to run the blog-build WorkflowTemplate and nothing else — it can’t, for instance, read Secrets in namespaces it has no business touching, because I never granted a cluster-wide ClusterRoleBinding out of laziness at 11pm. That temptation is real; the discipline is refusing it even when nobody’s reviewing the PR.


Audit trails: GitOps as evidence, not just convenience
#

Banking audit requirements exist to answer one question after the fact: what changed, who approved it, and when. In a regulated environment that’s a paper trail — change tickets, approval workflows, sign-offs. I don’t run change tickets at home. But I run something that gives me the same answer for free: GitOps.

Every manifest that defines the state of my cluster lives in Git. ArgoCD syncs from deployment/blog (and the equivalent paths for every other app) with prune: true and selfHeal: true, which means the cluster’s actual state and the Git repository’s declared state cannot drift silently — if they diverge, ArgoCD either corrects it or tells me. Kargo commits every promotion from preview to production as a Git commit against kustomization.yaml, which means “when did this go to production and on whose authority” has a literal, timestamped, git log answer with my name on the commit — no separate change record to lose track of, because the deployment mechanism is the audit log.

That’s the part of banking-grade thinking I find genuinely portable, more than any specific tool: if you can’t reconstruct what happened after the fact, you don’t actually know what happened, you’re just hoping nothing went wrong. GitOps gives a homelab that property almost as a side effect of being GitOps at all — I didn’t bolt on auditing, I got it by choosing a deployment model that happened to need it for a completely different reason (deployment safety, not compliance).


Secrets management: Sealed Secrets instead of plaintext in Git
#

This is the one every homelab writeup gets wrong at least once, myself included in an earlier iteration of this cluster. PCI-DSS requires that cardholder data — and by extension the credentials that protect it — never sit in plaintext anywhere they don’t strictly need to. The homelab version of that mistake is a secrets.yaml committed to a public or semi-public Git repo with a database password sitting in it in cleartext, discoverable by anyone who clones the history.

I run Sealed Secrets specifically to close that gap. Every secret is encrypted client-side against the cluster controller’s public key before it ever touches Git — what lands in the repository is ciphertext that’s useless outside that one controller. I converted every remaining plaintext secrets.yaml in this stack over to SealedSecrets deliberately, not because a regulator was checking, but because I’d internalised what a plaintext credential in version control actually costs once it’s out — you don’t get to un-leak it, you rotate everything downstream of it and hope you found every dependency. That asymmetry — cheap to prevent, expensive to recover from — is exactly the logic PCI-DSS is built on, and it holds just as well when the stakes are a personal blog instead of a card network.


Where the analogy breaks down — on purpose
#

I want to be direct about the limits here, because overclaiming is worse than saying nothing. My homelab has no third-party auditor, no penetration test cadence, no incident response retainer, no data processing agreements, no regulator who can fine me, and no actual cardholder or personal data flowing through it that carries legal weight. Framing any of this as “PCI-DSS compliant” or “POPIA compliant” would be false, and I’d correct anyone who said it about their own lab too. What I’ve borrowed is the habit of asking the questions those frameworks force you to ask — not the frameworks themselves, not their certification, and not their enforcement teeth.

The value of the exercise isn’t the badge. It’s that the questions are good questions regardless of who’s asking them.

Banking requirementWhat it forces you to askHomelab equivalent
Data residency (POPIA)Where does this data actually live?Local storage on cluster nodes I control; Sealed Secrets ciphertext-only in Git
Network segmentation (PCI-DSS)Can this system reach systems it has no reason to reach?CiliumNetworkPolicy per namespace, verified with Hubble flows
Access control / least privilege (PCI-DSS)Who can reach this, and can I prove it on demand?Keycloak OIDC/SSO across ArgoCD, Grafana, Gitea, Vaultwarden; scoped service-account RBAC
Audit trail (both)Can I reconstruct what changed, when, and on whose authority?ArgoCD sync history + Kargo promotion commits in Git
Encryption of sensitive data (PCI-DSS)Is this ever sitting in plaintext where it doesn’t need to be?Sealed Secrets end-to-end; cert-manager/Let’s Encrypt TLS on every route
Breach notification readiness (POPIA)Would I know if this leaked, and what it touched?Hubble flow visibility + Prometheus/Grafana/Graylog observability stack

The actual payoff
#

None of this made my homelab more available or faster to deploy to — if anything, Sealed Secrets and per-namespace network policies add friction I could have skipped. What it bought me is something quieter: I trust my own infrastructure the way I’d want a colleague’s infrastructure to be trustable if I had to review it. When I look at a CiliumNetworkPolicy I wrote six months ago, I can tell you why each rule exists. When I rotate a credential, I know exactly which Sealed Secret it lives in and which service consumes it. That’s not a compliance outcome. It’s just what happens when you stop treating “homelab” as a licence to be sloppy about the same things you’d never let slide at work.

Summary: POPIA and PCI-DSS don’t apply to a personal Kubernetes cluster, and I’m not pretending they do — but the discipline behind them (know where your data lives, segment your network, enforce least privilege, keep an audit trail, never let a secret sit in plaintext) is portable regardless of legal exposure, and building it into Cilium network policies, Keycloak SSO, GitOps promotion history, and Sealed Secrets has made my homelab a genuinely more trustworthy system to operate — not because a regulator is watching, but because the habit of asking “could I defend this decision after the fact” doesn’t switch off when I clock out.

Get in touch · View Resume · Connect on LinkedIn