Chapter 5. Encrypt Kubernetes Secrets in Git
Why this
You don’t need a cloud KMS to stop plaintext secrets in Git. SOPS + age lets you encrypt fields in YAML, commit safely, and decrypt only when applying on your machine.
1) Install SOPS and age (one-time)
- macOS:
brew install sops age - Windows (PowerShell):
winget install -e --id Mozilla.SOPSandwinget install -e --id FiloSottile.OpenSSH(age may be in package managers you use) - Linux (Debian/Ubuntu):
sudo apt install -y sops age
If your platform uses different packages, install SOPS and age via your package manager of choice.
2) Generate an age key (keep local)
age-keygen -o .sops.agekey
echo 'export SOPS_AGE_KEY_FILE=.sops.agekey' >> .env.sops
source .env.sops
Do not commit
.sops.agekey. Add it to.gitignore.
3) Define SOPS rules for Kubernetes Secrets
Create .sops.yaml at repo root:
# Encrypt Kubernetes Secret data/stringData by default
creation_rules:
- path_regex: infra/k8s/.*\.secret\.ya?ml$
encrypted_regex: '^(data|stringData)$'
age: []
# age recipient(s) will be added automatically from your key when you first encrypt
4) Create a sample Secret (unencrypted working copy)
Create infra/k8s/secret.secret.yaml:
apiVersion: v1
kind: Secret
metadata:
name: demo-secret
namespace: ship
type: Opaque
stringData:
API_TOKEN: "replace-me"
5) Encrypt it in-place
sops -e -i infra/k8s/secret.secret.yaml
git add infra/k8s/secret.secret.yaml .sops.yaml
git commit -m "chore(secrets): add encrypted Secret via SOPS"
Open the file—you should see ciphertext, plus a sops: metadata block.
6) Apply to the cluster (decrypt on the fly)
sops -d infra/k8s/secret.secret.yaml | kubectl apply -f -
kubectl -n ship get secret demo-secret -o yaml | grep -A3 data:
You’ll see base64 fields at runtime (normal for K8s), but Git still holds ciphertext.
7) Audit evidence (prove it’s encrypted)
grep -n "sops:" -n infra/k8s/secret.secret.yaml > artifacts/sops-audit.txt
echo "Wrote artifacts/sops-audit.txt"
Makefile helpers (optional)
sops-keygen:
@test -f .sops.agekey || age-keygen -o .sops.agekey
@echo "export SOPS_AGE_KEY_FILE=.sops.agekey" > .env.sops
@echo "Run: source .env.sops"
sops-encrypt:
sops -e -i infra/k8s/secret.secret.yaml
sops-decrypt:
sops -d infra/k8s/secret.secret.yaml > /tmp/secret.plain.yaml && \
echo "Decrypted to /tmp/secret.plain.yaml (do not commit)"
k8s-apply-sops:
sops -d infra/k8s/secret.secret.yaml | kubectl apply -f -
Add
.sops.agekeyto.gitignore:
Checklist
-
.sops.agekeygenerated and not committed -
.sops.yamlpresent with Kubernetes rules -
infra/k8s/secret.secret.yamlshows ciphertext in Git -
sops -d … | kubectl apply -f -works -
artifacts/sops-audit.txtsaved