Manage Secrets in Git with SOPS and age
Encrypt Kubernetes Secrets before committing them, and let Flux decrypt them in the cluster.
GitOps puts your cluster’s configuration in Git, and that includes Secrets. Committing a plain Kubernetes Secret exposes its values to anyone who can read the repository, because Secret data is only base64-encoded. Flux’s kustomize-controller can decrypt files encrypted with Mozilla SOPS, so you can commit encrypted Secrets and keep the whole system in Git. This guide uses age keys, the simplest option.
Install the tools and create a key
Install age and sops (for example brew install age sops), then generate a key pair:
age-keygen -o age.agekey
The output file holds the private key, and the command prints the public key (it starts with age1). Keep the private key out of Git. Back it up somewhere safe, because if you lose it you can’t decrypt your secrets. Flux 2.9 also adds support for age’s post-quantum cipher, if your policies call for it.
Give the cluster the private key
Store the private key as a Secret in the same namespace as the Flux Kustomization that will use it:
kubectl create secret generic sops-age \
--namespace=flux-system \
--from-file=age.agekey=/dev/stdin < age.agekey
The key in the Secret must end in .agekey. Delete the local age.agekey file or move it to a password manager once it’s safely stored elsewhere.
Configure SOPS in the repository
Create .sops.yaml at the root of your repository so everyone encrypts the same way. Replace the key with your public key:
creation_rules:
- path_regex: .*\.yaml
encrypted_regex: ^(data|stringData)$
age: age1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
encrypted_regex encrypts only the data and stringData fields, so everything else in the manifest stays readable for review and for Flux to process. You can list several age recipients (comma-separated) so teammates and the cluster key can all decrypt.
Encrypt a Secret
Write the Secret manifest locally, for example apps/webapp/db-credentials.yaml:
apiVersion: v1
kind: Secret
metadata:
name: db-credentials
namespace: webapp
stringData:
username: webapp
password: s3cr3t-value
Encrypt it in place:
sops --encrypt --in-place apps/webapp/db-credentials.yaml
Open the file: the username and password values are now ciphertext and a sops: block has been added with metadata. It’s safe to commit. To edit later, run sops apps/webapp/db-credentials.yaml, which decrypts in your editor and re-encrypts on save. To check what you’re about to commit, make sure the values are no longer readable.
Tell Flux to decrypt
Add a decryption block to the Flux Kustomization that applies the folder:
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: webapp
namespace: flux-system
spec:
interval: 10m
sourceRef:
kind: GitRepository
name: flux-system
path: ./apps/webapp
prune: true
decryption:
provider: sops
secretRef:
name: sops-age
Commit and push. During reconciliation, kustomize-controller decrypts the file in memory and applies a normal Secret to the cluster. Verify it:
flux reconcile kustomization webapp --with-source
kubectl get secret db-credentials -n webapp
Troubleshooting and good practice
If the Kustomization reports a decryption error, check that the sops-age Secret exists in the same namespace as the Kustomization, that its key name ends in .agekey, and that the file was encrypted to the matching public key. kubectl describe kustomization webapp -n flux-system shows the error.
- Use a different key per environment so a dev key can’t read production secrets.
- Rotate keys by adding the new recipient, re-encrypting (
sops updatekeys), then removing the old one. - Cloud KMS keys (AWS KMS, Azure Key Vault, Google Cloud KMS) and HashiCorp Vault or OpenBao work too, and avoid distributing key files. Configure them with workload identity and skip
secretRefwhere the platform provides credentials. - If you’d rather keep secrets out of Git entirely, use the External Secrets Operator to sync them from a vault, and let Flux manage only the
ExternalSecretobjects.
Popular Guides
-
Push vs Pull: How Flux Delivers to Kubernetes Why pull-based, Git-driven delivery changes how you deploy, secure, and recover clusters. -
Deploy Your First Application with a GitRepository and Kustomization Point Flux at manifests in Git using a Kustomization resource and watch it sync. -
Control Sync Behavior: Intervals, Prune, Drift Correction, and Rollback Decide when Flux applies changes, what it deletes, how to pause it, and how to undo a bad deploy. -
Automate Image Updates with Flux Image Automation Let Flux detect new container image tags and commit them back to Git. -
Configure Multi-Cluster with Flux Manage several clusters from one repository, with shared infrastructure and per-cluster differences. -
Flux CD vs Argo CD: Choosing a GitOps Tool Compare architecture, UI, Helm, secrets, tenancy, and image automation to pick the right tool.