Vault Agent Injector
The Vault Agent Injector is a Kubernetes mutating admission webhook (installed as part of the Vault Helm chart with injector.enabled: true). When a Pod with special annotations is created, the webhook injects two containers into the Pod spec: an init container (fetches secrets before the app starts) and optionally a sidecar container (keeps secrets refreshed throughout the Pod's lifetime).
Basic Annotations
apiVersion: apps/v1
kind: Deployment
metadata:
name: grade-api
namespace: grade-api
spec:
template:
metadata:
annotations:
# Required: enable the injector
vault.hashicorp.com/agent-inject: "true"
# Required: Vault role to authenticate with
vault.hashicorp.com/role: "grade-api"
# Required: the secret path to fetch
vault.hashicorp.com/agent-inject-secret-db: "secret/data/grade-api/db"
# Optional: control the output file format
# This creates /vault/secrets/db with the raw Vault JSON response
spec:
serviceAccountName: grade-api-sa
containers:
- name: grade-api
image: ghcr.io/your-org/grade-api:latest
# After injection, secrets are available at:
# /vault/secrets/db
# /vault/secrets/
Secret Templates
metadata:
annotations:
vault.hashicorp.com/agent-inject: "true"
vault.hashicorp.com/role: "grade-api"
# Fetch the secret
vault.hashicorp.com/agent-inject-secret-db: "secret/data/grade-api/db"
# Template the output into a specific format
# This creates /vault/secrets/db as a .env file
vault.hashicorp.com/agent-inject-template-db: |
{{- with secret "secret/data/grade-api/db" -}}
export DB_HOST="{{ .Data.data.host }}"
export DB_USERNAME="{{ .Data.data.username }}"
export DB_PASSWORD="{{ .Data.data.password }}"
{{- end }}
# Multiple secrets — one annotation pair per secret
vault.hashicorp.com/agent-inject-secret-redis: "secret/data/grade-api/redis"
vault.hashicorp.com/agent-inject-template-redis: |
{{- with secret "secret/data/grade-api/redis" -}}
export REDIS_URL="{{ .Data.data.url }}"
{{- end }}
# Control when Vault Agent refreshes secrets
# Refresh every 5 minutes for short-lived dynamic secrets
vault.hashicorp.com/agent-inject-secret-db-creds: "database/creds/grade-api-role"
vault.hashicorp.com/agent-inject-template-db-creds: |
{{- with secret "database/creds/grade-api-role" -}}
DB_USERNAME="{{ .Data.username }}"
DB_PASSWORD="{{ .Data.password }}"
DB_LEASE_DURATION="{{ .LeaseDuration }}"
{{- end }}
# POSIX signal to send to app when secrets are renewed
vault.hashicorp.com/agent-inject-command-db-creds: "kill -HUP $(pidof grade-api)"
Init Container Mode
# If you only need secrets at startup and don't need renewal:
vault.hashicorp.com/agent-inject: "true"
vault.hashicorp.com/role: "grade-api"
vault.hashicorp.com/agent-pre-populate-only: "true" # No sidecar, only init
vault.hashicorp.com/agent-inject-secret-db: "secret/data/grade-api/db"
# The init container exits after fetching secrets.
# No sidecar running alongside your app.
# Secrets are NOT refreshed automatically — use this for static secrets only.
Vault Secrets Operator
The Vault Secrets Operator (VSO) is an alternative to the Injector. Instead of injecting sidecars, VSO manages Kubernetes Secrets — it reads from Vault and creates/updates Kubernetes Secrets. Your Pods consume standard Kubernetes Secrets (env vars or volume mounts), with no Vault-specific annotations or sidecars.
Installing VSO
helm repo add hashicorp https://helm.releases.hashicorp.com
helm install vault-secrets-operator hashicorp/vault-secrets-operator \
--namespace vault-secrets-operator-system \
--create-namespace \
--set defaultVaultConnection.enabled=true \
--set defaultVaultConnection.address=http://vault.vault.svc.cluster.local:8200
VaultAuth CRD
# VaultAuth — defines how to authenticate to Vault for this namespace
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultAuth
metadata:
name: grade-api-auth
namespace: grade-api
spec:
method: kubernetes
mount: kubernetes
kubernetes:
role: grade-api
serviceAccount: grade-api-sa
audiences:
- vault
VaultStaticSecret CRD
# VaultStaticSecret — creates/updates a Kubernetes Secret from a Vault path
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultStaticSecret
metadata:
name: grade-api-db-secret
namespace: grade-api
spec:
vaultAuthRef: grade-api-auth # Reference the VaultAuth above
type: kv-v2
mount: secret # KV secrets engine mount path
path: grade-api/db # Path within the mount
destination:
name: grade-api-db-secret # Name of the Kubernetes Secret to create
create: true # Create if it doesn't exist
transformation:
excludeRaw: true # Only include the KV data, not Vault metadata
refreshAfter: 30s # How often to sync from Vault
rolloutRestartTargets: # Restart Deployments when secret updates
- kind: Deployment
name: grade-api
# The resulting Kubernetes Secret looks like:
# apiVersion: v1
# kind: Secret
# metadata:
# name: grade-api-db-secret
# namespace: grade-api
# data:
# host: cG9zdGdyZXMuZ3JhZGUtYXBpLnN2Yy5jbHVzdGVyLmxvY2Fs
# username: Z3JhZGVfYXBpX3VzZXI=
# password: c3VwZXItc2VjcmV0LWRiLXBhc3N3b3Jk
# Consume it normally in the Deployment:
spec:
template:
spec:
containers:
- name: grade-api
envFrom:
- secretRef:
name: grade-api-db-secret
Injector vs VSO
Use Vault Agent Injector when:
- You need dynamic secrets (database credentials, AWS tokens) with in-Pod renewal
- You want secrets as files (not Kubernetes Secrets) to minimize secret surface area
- You need POSIX-signal-based secret refresh without Pod restart
- Your app reads config from files naturally (12-factor apps)
Use Vault Secrets Operator when:
- You have legacy apps that read from Kubernetes Secrets (env vars)
- You want a standard Kubernetes interface — GitOps-friendly, no sidecar overhead
- You need automatic Deployment rollouts when secrets change
- You're using tools that already know how to consume Kubernetes Secrets (Helm values, etc.)
Exercises
secret/grade-api/config. Deploy a Pod with injector annotations referencing that path. Exec into the Pod and verify /vault/secrets/config contains the rendered values.
key=value format). Verify the format in the mounted file.
Key Takeaways & Next Steps
- Vault Agent Injector uses annotations on the Pod spec — the mutating webhook injects the sidecar automatically
- Templating lets you render secrets into any format your app expects (env files, JSON, TOML)
- Vault Secrets Operator is the modern approach — it creates Kubernetes Secrets that any app can consume
- VSO's
rolloutRestartTargetsautomatically restarts Deployments when Vault secrets change - Both approaches use the Kubernetes auth method from Part 2 — same service account, same role
Next in This Track
In Part 4: Dynamic Secrets & PKI, we use Vault's database engine to generate on-demand PostgreSQL credentials with automatic expiry, and use the PKI engine to issue X.509 certificates for mTLS between services.