Secret Management
Kubernetes Secrets are only base64-encoded, so they can’t live in Git as-is. The convention KSail is
built around is to encrypt them at rest with SOPS: you commit *.enc.yaml files
whose secret values are ciphertext, and a GitOps engine (Flux or ArgoCD)
decrypts them transparently on the way into the cluster. Keys stay out of Git; the repository stays the
source of truth.
This guide covers the recommended layout, the ksail workload cipher commands that manage it, and how
SOPS hands off to a runtime secret store for production.
The convention
Section titled “The convention”Encrypted files follow a naming convention — *.enc.yaml — so they’re easy to spot and to target. A
repo-root .sops.yaml declares creation_rules that tell SOPS what to encrypt and with which key:
creation_rules: # Local environment — its own age recipient - path_regex: k8s/clusters/local/.*\.enc\.yaml$ encrypted_regex: ^(data|stringData)$ age: age1localrecipient... # Production — a separate key, so a leaked local key can't decrypt prod - path_regex: k8s/clusters/prod/.*\.enc\.yaml$ encrypted_regex: ^(data|stringData)$ age: age1prodrecipient...path_regexscopes a rule to a set of paths — typically one rule per environment, each under its per-cluster overlay (clusters/local/…,clusters/prod/…).encrypted_regex: ^(data|stringData)$encrypts only the Secret values — keys, names, and other metadata stay readable, so diffs and reviews still make sense.ageis the recipient public key. Using a separate key per environment keeps blast radius small: a compromised local key can’t unlock production secrets.
Encrypt, edit, and rotate
Section titled “Encrypt, edit, and rotate”ksail workload cipher wraps SOPS so the same toolchain ships in the binary. It reads .sops.yaml, so
you don’t pass recipients on the command line — the matching creation_rule picks the key.
ksail workload cipher encrypt k8s/clusters/local/secret.enc.yaml # encrypt in placeksail workload cipher edit k8s/clusters/local/secret.enc.yaml # decrypt → $EDITOR → re-encryptksail workload cipher decrypt k8s/clusters/local/secret.enc.yaml # decrypt to stdoutksail workload cipher rotate k8s/clusters/local/secret.enc.yaml # re-key with a fresh data keyA typical first secret: write a normal manifest, then encrypt it.
kubectl create secret generic my-app \ --from-literal=token=s3cr3t \ --dry-run=client -o yaml > k8s/clusters/local/secret.enc.yaml
ksail workload cipher encrypt k8s/clusters/local/secret.enc.yamlCommit the result — only data/stringData are ciphertext — then push and reconcile as usual.
How decryption happens
Section titled “How decryption happens”Decryption is transparent during GitOps sync — there’s no manual plugin step. When a GitOps engine is active and SOPS is enabled, KSail provisions the age private key into the cluster and wires it into the engine; encrypted files in your source directory are decrypted automatically as they’re applied.
- Flux — KSail creates a
sops-ageSecret influx-systemand Flux Kustomizations reference it viaspec.decryption.secretRef. - ArgoCD — KSail creates the
sops-ageSecret inargocdand installs a Config Management Plugin sidecar that decrypts manifests before render. See ArgoCD ApplicationSet — SOPS Age Integration.
Configure resolution and behavior via spec.cluster.sops in ksail.yaml — see
Declarative Configuration.
Keys live outside Git
Section titled “Keys live outside Git”The age public recipient lives in .sops.yaml; the private key never enters the repository.
- Locally, place it at
~/.config/sops/age/keys.txt(ksail workload cipher import AGE-SECRET-KEY-1…drops a key there for you). - In CI, provide it as a secret — e.g. an
SOPS_AGE_KEYvariable written to~/.config/sops/age/keys.txtbefore push/reconcile — so the pipeline can decrypt when it needs to. See CI/CD Integration.
Beyond at-rest: the runtime store
Section titled “Beyond at-rest: the runtime store”SOPS is the right tool for at-rest secrets in Git, but it isn’t a full secret-management plane: it doesn’t rotate credentials on its own or distribute them across many workloads. The recommended production pattern treats SOPS as a bootstrap seed:
- Commit a small set of SOPS-encrypted secrets that seed a runtime secret store (a vault) — for example, the store’s root credential.
- Run an operator (such as External Secrets Operator) that reads from that store and syncs/rotates the actual Secrets in-cluster. Day-to-day secrets then live in the store, not in Git.
- Bootstrap-critical values — a cloud token a controller needs at startup, before the store is
reachable — can instead be delivered through Flux post-build
${var}substitution from thebootstrap/layer.
This keeps Git authoritative for what exists while the runtime store owns rotation and distribution. See the Reference Architecture for how this fits an end-to-end platform.
What’s next
Section titled “What’s next”-
Project Structure & GitOps Layout → — where
*.enc.yamlfiles and.sops.yamlrules sit in a layered repository. -
Multi-Environment Workflows → — driving a key-per-environment setup with one repository and a config per cluster.
-
Reference Architecture → — the SOPS-seeds-a-vault pattern in a complete platform.