How-To: Manage Secrets with SOPS¶
All secrets in HomeKube are encrypted with SOPS using a dedicated GPG key. Flux decrypts them automatically at apply time. No plaintext secrets are ever committed to Git.
See ADR-010 for the full decision record.
Prerequisites¶
The HomeKube GPG key must be imported on your local machine. Verify it exists:
Expected output:
sec rsa4096 2026-03-21 [SCEAR]
CF7169E94481219626AF34290D18AE7E58FB2D45
uid [ultimate] HomeKube SOPS <homekube@local>
If the key is missing, see Restore the GPG key after minikube delete below.
Encrypted Files¶
| File | K8s Secret Name | Contents |
|---|---|---|
identity/keycloak-secret.sops.yaml |
keycloak-admin-secret |
Keycloak admin password |
identity/realm-config.sops.yaml |
keycloak-realm-config (ConfigMap) |
Full realm JSON: clients, roles, users |
observability/grafana-admin-secret.sops.yaml |
grafana-admin-secret |
Grafana admin username + password |
observability/grafana-oidc-secret.sops.yaml |
grafana-oidc-secret |
Grafana OIDC client secret |
Edit an Existing Secret¶
SOPS decrypts the file, opens it in $EDITOR, and re-encrypts on save. Then push:
git add identity/keycloak-secret.sops.yaml
git commit -m "chore: rotate keycloak admin password"
git push origin main
Flux picks up the change within 1 hour, or force immediately:
Restart required for some changes
Keycloak and Grafana read secrets at startup. After changing their passwords or OIDC secrets, restart the affected deployment:
Create a New Secret¶
- Write a plaintext manifest to the repo path matching
*.sops.yaml:
cat > identity/my-new-secret.sops.yaml <<EOF
apiVersion: v1
kind: Secret
metadata:
name: my-new-secret
namespace: identity
stringData:
my-key: "my-plaintext-value"
EOF
- Encrypt in-place (SOPS finds
.sops.yamlin the repo root):
- Add to
identity/kustomization.yamlresources list, commit, and push.
Rotate a Secret Value¶
- Edit the file:
sops identity/keycloak-secret.sops.yaml - Change the value, save, and exit — SOPS re-encrypts automatically
- Commit and push
- Restart the affected workload
Restore the GPG Key After minikube delete¶
The sops-gpg Kubernetes secret is stored in the cluster and is deleted with minikube delete. Flux cannot decrypt secrets until it is restored.
If the GPG private key still exists locally (most common case):
gpg --export-secret-keys --armor CF7169E94481219626AF34290D18AE7E58FB2D45 | \
kubectl create secret generic sops-gpg \
-n flux-system \
--from-file=sops.asc=/dev/stdin
If the key was lost (e.g. new machine), generate a new one and re-encrypt all secrets:
# 1. Generate new key
gpg --batch --gen-key <<'EOF'
%no-protection
Key-Type: RSA
Key-Length: 4096
Name-Real: HomeKube SOPS
Name-Email: homekube@local
Expire-Date: 0
EOF
# 2. Get the new fingerprint
gpg --list-secret-keys homekube@local
# 3. Update .sops.yaml with the new fingerprint
# Edit .sops.yaml: replace old fingerprint with new one
# 4. Re-encrypt every *.sops.yaml file
for f in $(find . -name "*.sops.yaml"); do
sops updatekeys "$f"
done
# 5. Commit and push
git add .sops.yaml $(find . -name "*.sops.yaml")
git commit -m "chore: rotate SOPS GPG key"
git push origin main
# 6. Store new private key in cluster
gpg --export-secret-keys --armor <NEW_FINGERPRINT> | \
kubectl create secret generic sops-gpg \
-n flux-system \
--from-file=sops.asc=/dev/stdin
How .sops.yaml Works¶
# .sops.yaml (repo root)
creation_rules:
- path_regex: \.sops\.yaml$ # applies to all *.sops.yaml files
encrypted_regex: ^(data|stringData)$ # encrypts only these YAML fields
pgp: CF7169E94481219626AF34290D18AE7E58FB2D45 # HomeKube GPG key
- Only
dataandstringDatafields are encrypted — metadata stays human-readable - Files not matching
*.sops.yamlare never encrypted (regular manifests stay plaintext) - Flux reads
.sops.yamlto find the key reference, then usessops-gpgto decrypt