Skip to content

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:

gpg --list-secret-keys homekube@local

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 identity/keycloak-secret.sops.yaml

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:

flux reconcile kustomization identity --with-source

Restart required for some changes

Keycloak and Grafana read secrets at startup. After changing their passwords or OIDC secrets, restart the affected deployment:

kubectl rollout restart deployment/keycloak -n identity
kubectl rollout restart deployment/kube-prometheus-stack-grafana -n monitoring


Create a New Secret

  1. 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
  1. Encrypt in-place (SOPS finds .sops.yaml in the repo root):
sops --encrypt --in-place identity/my-new-secret.sops.yaml
  1. Add to identity/kustomization.yaml resources list, commit, and push.

Rotate a Secret Value

  1. Edit the file: sops identity/keycloak-secret.sops.yaml
  2. Change the value, save, and exit — SOPS re-encrypts automatically
  3. Commit and push
  4. 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 data and stringData fields are encrypted — metadata stays human-readable
  • Files not matching *.sops.yaml are never encrypted (regular manifests stay plaintext)
  • Flux reads .sops.yaml to find the key reference, then uses sops-gpg to decrypt

Verify Decryption Works

# Manually decrypt to stdout (does not modify the file)
sops --decrypt identity/keycloak-secret.sops.yaml

# Check Flux can decrypt (will show applied revision on success)
flux reconcile kustomization identity --with-source