GitOps on Kubernetes with ArgoCD and SOPS
Declarative cluster state in a single Git repo, with encrypted secrets that age-encrypt at rest and decrypt only at sync time. A pattern I've now used across three different homelab clusters.
GitOps on Kubernetes with ArgoCD and SOPS
Why GitOps
The promise of GitOps isn't just "infrastructure as code." It's that the cluster's current state is observable in a single place — and any drift between Git and the cluster is a bug to fix, not a thing to live with. ArgoCD reconciles continuously; if someone kubectl applys something out of band, ArgoCD shows the application as OutOfSync and (depending on the sync policy) reverts it on the next pass.
Why ArgoCD
A few reasons that mattered for my homelab. The Application CRD model maps cleanly to "one ArgoCD Application per logical service" — easy to reason about. The web UI is genuinely useful when debugging a sync that won't go through, which I appreciate more than I expected to. And the ApplicationSet controller turns "I want this pattern of apps across three environments" into a single YAML, which scales nicely as the cluster grows.
Why SOPS instead of Sealed Secrets
SOPS encrypts the secret values in the YAML file itself, leaving keys and structure readable. Sealed Secrets encrypts the whole resource and produces an opaque blob. SOPS-encrypted manifests are reviewable in PRs — you can see which secrets changed without seeing their values. That's the difference between "encrypted secret management" and "encrypted secret management that's actually nice to use."
The wiring for ArgoCD is through the Kustomize SOPS plugin: ArgoCD invokes Kustomize at sync time, the plugin decrypts the SOPS-managed fields, and the resulting plain manifests are applied to the cluster. The decrypted values never touch Git.
Disaster recovery test
The real test: nuke the cluster, install a fresh one, install ArgoCD with one Helm chart, hand it the age key and point its root Application at the Git repo. Within ~10 minutes the entire cluster state is restored from Git, secrets and all. I've now done this drill three times on purpose.
Caveats
The age key itself has to live outside Git. I keep mine in a password manager with a copy in a bank safety deposit box. The "single age key" model is the simplest; for production setups, age recipients per-environment is the more scalable answer.
The Kustomize SOPS plugin requires a custom ArgoCD image (or a sidecar plugin via the v2.6+ Config Management Plugin API). It's one of those setups that feels heavy when you wire it up the first time and then disappears into the background once it works.