This guide walks through the complete process of adding a new secret to Vault and having it automatically synced to Kubernetes via the Vault Secrets Operator (VSO).
terraform-k8s-infra and k8s-cluster-config reposIf this is a new namespace that doesn't already have VSO configured, add it to the namespaces list in terraform-k8s-infra/envs/lab/main.tf:
module "vso_k8s_per_ns" {
source = "../../modules/vault-k8s-auth-vso"
# ...
namespaces = [
"monitoring",
"cloudflared-connector",
"mimir",
"authentik",
"argocd",
"harbor",
"life-ops",
"nextcloud",
"technitium",
"NEW-NAMESPACE" # ← Add here
]
}
This creates:
vso-NEW-NAMESPACE-read (read access to kv/data/NEW-NAMESPACE/*)vso-NEW-NAMESPACE (bound to SA vso-auth in NEW-NAMESPACE)In the same file, add a secret placeholder:
module "vault_secrets_placeholder" {
# ...
secrets = {
# ... existing secrets ...
my_app_secret = {
path = "NEW-NAMESPACE/my-secret"
data = {
username = "admin"
password = "PLACEHOLDER_UPDATE_IN_VAULT"
}
}
}
}
Important: The path must start with the namespace name (matches the Vault policy).
cd terraform-k8s-infra/envs/lab
terraform plan # Review changes
terraform apply # Apply
vault kv put kv/NEW-NAMESPACE/my-secret \
username="admin" \
password="REAL_SECRET_VALUE"
Or use the Vault UI at https://vault.homelab.vyanh.uk.
Create the following files in k8s-cluster-config/core-components/<app>/resources/:
sa.yaml — ServiceAccount:
apiVersion: v1
kind: ServiceAccount
metadata:
name: vso-auth
namespace: NEW-NAMESPACE
vault-auth.yaml — VaultAuth CR:
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultAuth
metadata:
name: vso-auth
namespace: NEW-NAMESPACE
spec:
method: kubernetes
mount: kubernetes
kubernetes:
role: vso-NEW-NAMESPACE
serviceAccount: vso-auth
my-secret-vaultstaticsecret.yaml — VaultStaticSecret CR:
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultStaticSecret
metadata:
name: my-secret
namespace: NEW-NAMESPACE
spec:
type: kv-v2
mount: kv
path: NEW-NAMESPACE/my-secret
refreshAfter: 60s
vaultAuthRef: vso-auth
destination:
create: true
name: my-secret
In your Helm values, reference the K8s Secret:
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: my-secret
key: password
Or mount as a volume:
volumes:
- name: secrets
secret:
secretName: my-secret
# In k8s-cluster-config
git add core-components/<app>/resources/
git commit -m "feat: add VSO secrets for <app>"
git push
# Check VSO synced the secret
kubectl get secret my-secret -n NEW-NAMESPACE -o yaml
# Check VaultStaticSecret status
kubectl get vaultstaticsecret -n NEW-NAMESPACE
# Check for any errors
kubectl describe vaultstaticsecret my-secret -n NEW-NAMESPACE
namespaces list (if new)secrets mapterraform applysa.yaml, vault-auth.yaml, *-vaultstaticsecret.yaml in k8s-cluster-config| Issue | Check |
|---|---|
| Secret not appearing | kubectl describe vaultstaticsecret for errors |
| Authentication failed | Verify SA vso-auth exists in namespace |
| Policy denied | Check Vault policy allows the path |
| Placeholder values | Did you update real values in Vault? |