Choosing How Kubernetes Workloads Get Secrets from Vault
A decision guide for delivering Vault secrets to Kubernetes workloads: compare Agent Injector, app-native SDK, Secrets Operator, and CSI, then configure Kubernetes auth and validate.
10 Apr 2026, 20:15 UTC

Connecting Kubernetes workloads to HashiCorp Vault is rarely about whether you can authenticate — it is about how the secret actually reaches the application. The delivery pattern you pick determines whether secrets sit in etcd, on disk, or only in memory, and who owns the operational burden when tokens expire or Vault is unreachable.
Fix these constraints before choosing: Can application code change? Who owns the platform components versus the app? How fast must rotation propagate? Are secrets allowed to exist as native Kubernetes Secrets in etcd? Do you need audit entries tied to a per-pod identity?
Comparing the Four Supported Patterns
| Pattern | Code change | Where secrets land | Owner | Best fit |
|---|---|---|---|---|
| Vault Agent Injector | No | Files in pod (sidecar) | Platform team | Legacy or polyglot apps |
| App-native SDK | Yes | Application memory | App team | Fine-grained audit, dynamic secrets |
| Vault Secrets Operator | No | Native K8s Secrets | Platform team | Consumers that require Secret objects |
| Secrets Store CSI + Vault provider | No | Mounted volume | Platform team | Standardized volume-based delivery |
All four should use the Kubernetes auth method for workload identity: the pod's service account token is exchanged for a Vault token scoped by namespace and service account.
Trade-offs That Actually Matter
Agent Injector: webhook availability becomes a dependency
The Injector works through a mutating admission webhook that adds an agent sidecar, which renders secrets to files inside the pod. No code changes are needed, and rotation can trigger re-rendering. The catch: pod scheduling now depends on the webhook. With failurePolicy: Fail, an unavailable injector blocks new pods cluster-wide; with Ignore, pods start silently without secrets and crash-loop in confusing ways. Run multiple injector replicas and decide the failure policy deliberately.
App-native SDK: most control, most per-team work
Authenticating directly from code keeps secrets in memory only and gives the finest audit granularity. But every team and language must implement JWT login, token renewal, and retry behavior consistently. Inconsistent implementations are a common source of production incidents when tokens expire.
Secrets Operator and CSI: less app awareness, wider read surface
The Vault Secrets Operator syncs Vault paths into native Kubernetes Secrets via custom resources — ideal when the consumer is another controller that expects Secret objects. The CSI driver mounts secrets as a volume instead. Both minimize app awareness of Vault, but Operator-synced material is only as safe as your etcd encryption, RBAC, and audit controls. Prefer the Operator for material the cluster already handles as Secrets.
Avoid injecting high-value secrets as environment variables in any pattern: they are visible in pod specs and can leak into crash dumps. Prefer rendered files with restrictive permissions or in-memory use.
Concrete Setup: Kubernetes Auth Plus the Agent Injector
Assumptions: Vault 1.x with KV v2 mounted at secret/, a Vault CLI authenticated with a token allowed to configure auth methods and policies, and the Vault Agent Injector already installed in the cluster. Annotation keys and auth parameters are version-sensitive — confirm them against the documentation for your Vault, agent, and Kubernetes versions.
1. Enable and configure the Kubernetes auth method
Run from a machine with Vault CLI access and a privileged token. Replace placeholders with your cluster's values; the CA cert is your cluster's API server CA.
vault auth enable kubernetes vault write auth/kubernetes/config \ kubernetes_host="https://:6443" \ kubernetes_ca_cert=@
Kubernetes auth depends on token issuer and audience details that changed across Kubernetes releases (legacy versus projected bound service account tokens). A misconfiguration here typically surfaces as permission denied at login time.
2. Write the policy and role
KV v2 path nuance: the CLI (vault kv put secret/app/db) accepts the mount path, but policies and raw API calls must include the data/ prefix for reads and metadata/ for listing. Misaligned paths between policy and application requests are the most common failure in this setup.
vault policy write myapp - <3. Annotate the deployment
Add these annotations to the pod template of a Deployment in namespace
myapp-nsrunning as service accountmyapp-sa:vault.hashicorp.com/agent-inject: "true" vault.hashicorp.com/role: "myapp" vault.hashicorp.com/agent-inject-secret-config: "secret/myapp/config"The agent renders the secret to a file under
/vault/secrets/inside the pod. Confirm exact annotation keys against your injector version before applying.Validation
- Run
vault auth listandvault read auth/kubernetes/role/myappto confirm the method, namespace, and service-account bindings match intent.- In a scratch namespace, deploy one annotated pod and verify the rendered file contents appear under
/vault/secrets/, then check the Vault audit log for a login entry tied to that pod's service account.- Rotate the value with
vault kv put secret/myapp/config ...and confirm re-rendering per your agent template configuration.- Revoke the role's tokens and confirm access is lost at TTL expiry.
Limitations
This guide covers well-established behavior, but annotation keys, auth config parameters, and Operator/CSI custom-resource fields evolve between releases — verify against version-matched documentation. The example was not executed here; validate it in a non-production namespace first. If you change webhook
failurePolicyor auth role bindings, treat it as a state-changing operation and keep the prior configuration values recorded so you can restore them.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.