Vault Agent Injector: Sidecar Secrets in Kubernetes
How Vault injects, renders, and refreshes secrets in pods without restarting your app
Vault Agent Injector: Sidecar Secrets in Kubernetes
How Vault injects, renders, and refreshes secrets in pods without restarting your app

Kubernetes and Vault logos on a computer screen. Image by the author.
The Vault Agent Injector is a Kubernetes admission webhook that modifies pods at creation time and adds Vault Agent init and sidecar containers. This allows applications to read secrets from files on disk instead of talking to Vault directly.
This is the fourth article from my Vault series. In the previous three articles, I covered:
- What Vault is and what its purpose is
- How to work with Vault locally
- How to integrate it with Kubernetes and access secrets
If you are new to Vault, I recommend starting with those articles first. This one builds on top of them and assumes you already understand basic concepts like policies, authentication methods, and KV secrets.
In this article, we focus fully on the Vault Agent Injector using the sidecar pattern. The goal is not just to make it work, but to understand why each piece exists and what problem it solves.
Why Applications Should Not Talk to Vault Directly
Vault Agent is a helper process that runs next to your application. It handles a few critical tasks:
- Authenticating to Vault
- Fetching secrets
- Renewing Vault tokens
- Refreshing secrets when they change
- Writing secrets to files
With Vault Agent in place, your application never calls Vault APIs. It does not manage tokens. It only reads files from disk.
This separation is important. When an application talks to Vault directly:
- It must understand Vault APIs
- It must store and renew tokens
- It often needs a restart when secrets change
Vault Agent removes all of that logic from the app. Vault-related concerns stay outside the application boundary. The app stays focused on its own job.
What the Vault Agent Injector Does on Kubernetes
On Kubernetes, Vault Agent runs using the vault-k8s integration. This integration relies on a Mutating Admission Webhook.
The injector adds three things to the Pod:
- An init container that runs once before the app starts
- A Vault Agent sidecar container that runs next to the app
- A shared volume where secrets are written
You don’t need to define these containers yourself. You only add annotations. The application container remains unchanged.
Here is the flow:

Vault Agent Diagram
The Role of Init Container and Sidecar
Vault Agent supports two execution phases:
Init Container
The init container:
- Runs before the application container starts
- Authenticates to Vault
- Fetches secrets
- Writes them to disk
- Exits
This guarantees that secrets exist before the app starts.
Sidecar Container
The sidecar:
- Starts after the init container
- Runs for the entire lifetime of the Pod
- Renews the Vault token
- Watches secrets
- Re-renders files when values change
This is what allows secrets to change without restarting the application.
If the sidecar pattern or Kubernetes basics still feel unclear, I cover them in detail in my ebook [Master Kubernetes from Scratch](http://Master Kubernetes from Scratch), where I explain core Kubernetes concepts step by step.
Hands-on: Inject and Read Secrets with the Vault Agent
Step 1: Install Vault with the Injector Enabled
For this demo, we use Vault in dev mode. This is not for production, but it keeps the setup simple for testing purposes.
helm install vault hashicorp/vault \
--namespace vault \
--create-namespace \
--set server.dev.enabled=true \
--set injector.enabled=true
Step 2: Confirm the Injector Is Running
Verify the created resources:
kubectl get all -n vault
You should see:
vault-0pod (Vault server)vault-agent-injectordeployment- Services for Vault and the injector
This confirms the injector is running.
Step 3: Allow Vault to Trust Kubernetes
Vault does not trust Kubernetes by default. We must explicitly configure this trust.
First, enable the Kubernetes auth method:
kubectl exec -n vault vault-0 -- vault auth enable kubernetes
Then configure it:
kubectl exec -n vault -i vault-0 -- vault write auth/kubernetes/config \
token_reviewer_jwt="$(kubectl exec -n vault vault-0 -- cat /var/run/secrets/kubernetes.io/serviceaccount/token)" \
kubernetes_host="https://kubernetes.default.svc:443" \
kubernetes_ca_cert="$(kubectl exec -n vault vault-0 -- cat /var/run/secrets/kubernetes.io/serviceaccount/ca.crt)"
This setup allows Vault to:
- Verify Service Account tokens
- Confirm pod identity using the Kubernetes API
Without this step, authentication will fail later.
Step 4: Store a Test Secret in Vault
kubectl exec -n vault vault-0 -- vault kv put secret/my-app/config \
username="demo-user" \
password="demo-password"
This creates a KV v2 secret in Vault.
Verify it:
kubectl exec -n vault vault-0 -- vault kv get secret/my-app/config
This secret doesn’t exist as a Kubernetes Secret because Vault manages it.
Step 5: Limit Access with Policies
kubectl exec -n vault -i vault-0 -- vault policy write my-app - <<EOF
path "secret/data/my-app/*" {
capabilities = ["read"]
}
EOF
This policy allows read access only to this app’s secrets.
Step 6: Bind Kubernetes Identity to Vault Access
kubectl exec -n vault vault-0 -- vault write auth/kubernetes/role/my-app \
bound_service_account_names=my-app \
bound_service_account_namespaces=app \
policies=my-app \
ttl=1h
This role binds together:
- A namespace
- A Service Account
- A Vault policy
Only pods that match all three can authenticate and read secrets.
Step 7: Create the Application Namespace and Service Account
kubectl create ns app
kubectl create sa my-app -n app
These values must match the role configuration exactly.
Step 8: Tell the Injector What to Inject
apiVersion: v1
kind: Pod
metadata:
name: my-app
namespace: app
annotations:
vault.hashicorp.com/agent-inject: "true"
vault.hashicorp.com/role: "my-app"
vault.hashicorp.com/agent-inject-secret-database-config: "secret/data/my-app/config"
vault.hashicorp.com/agent-inject-template-database-config: |
{{- with secret "secret/data/my-app/config" -}}
export DB_USERNAME="{{ .Data.data.username }}"
export DB_PASSWORD="{{ .Data.data.password }}"
{{- end -}}
spec:
serviceAccountName: my-app
containers:
- name: my-app
image: nginx:latest
command: ["/bin/sh"]
args: ["-c", "while true; do sleep 30; done"]
These annotations act as a contract between your Pod and the injector.
agent-inject: trueenables injectionroleselects the Vault roleagent-inject-secret-*defines the Vault path and output file nameagent-inject-template-*defines how the secret is rendered
The file name comes from the annotation suffix. Multiple annotations create multiple files.
How Vault Agent Templates Work
Templates run inside the Vault Agent, not inside your application.
They:
- Read data from Vault
- Render it into files
- Re-render when the data changes
In this example:
- Values are mapped to environment variable exports
- The file is written to
/vault/secrets/database-config
Your application can source the file or read it directly.
Step 9: Verify Injection
kubectl get pods -n app
You should see 2/2 containers running.
After injection, the Pod contains:
vault-agent-initvault-agent- A shared secrets volume
Step 10: Inspect Vault Agent Logs
Init container logs:
kubectl logs my-app -c vault-agent-init -n app
Sidecar logs:
kubectl logs my-app -c vault-agent -n app
These logs show authentication, token renewal, and template rendering.
Step 11: Read the Injected Secret
kubectl exec -it -n app my-app -c my-app -- cat /vault/secrets/database-config
You should see:
export DB_USERNAME="demo-user"
export DB_PASSWORD="demo-password"
Step 12: Update Secrets Without Restarting the App
Update the secret:
kubectl exec -n vault vault-0 -- vault kv put secret/my-app/config \
username="demo-user" \
password="demo-password2"
Vault creates a new version of the secret. The application keeps running.
How the Sidecar Detects and Applies Changes
The Vault Agent sidecar:
- Renews the Vault token
- Checks for secret updates
- Re-renders templates
For KV v2 secrets, the default render interval is 5 minutes.
You can change it using:
template_config {
static_secret_render_interval = "10m"
}
After the interval:
kubectl exec -it -n app my-app -c my-app -- cat /vault/secrets/database-config
The updated value appears without restarting the Pod (password=”demo-password2").
Sidecar logs confirm this:
agent.auth.handler: renewed auth token
agent: rendered "(dynamic)" => "/vault/secrets/database-config"
Conclusion
In this article, you learned that with the Vault Agent Injector and the sidecar pattern:
- Applications never talk to Vault directly
- Secrets live outside the app lifecycle
- Token renewal happens automatically
- Secret updates do not require restarts
- Access stays limited by namespace, Service Account, and policy
I hope these Vault series have been useful! Thanks for reading!
메타데이터
- post_id
- f0ae983542f7
- slug
- vault-agent-injector-sidecar-secrets-in-kubernetes-f0ae983542f7
- url
- https://medium.com/curious-devs-corner/vault-agent-injector-sidecar-secrets-in-kubernetes-f0ae983542f7
- canonical_url
- https://medium.com/curious-devs-corner/vault-agent-injector-sidecar-secrets-in-kubernetes-f0ae983542f7
- author_url
- https://medium.com/@kirshiyin
- status
- ok
- fetched_at
- 2026-06-14 11:28:49