How to Use Reloader with Vault External Secrets Operator Pattern#
This guide explains how to set up HashiCorp Vault with the External Secrets Operator (ESO), combined with Stakater Reloader for automatic pod restarts when secrets change.
Authentication Options#
ESO supports two authentication methods with Vault:
| Method | Description | Use Case |
|---|---|---|
| Token | Uses a Vault token stored in a K8s Secret | Simpler setup, good for development |
| Kubernetes Auth | Uses Kubernetes ServiceAccount tokens for authentication | More secure, no static credentials, recommended for production |
Choose the authentication method that best fits your security requirements and proceed to the corresponding section.
Overview#
┌─────────────────────────────────────────────────────────────────────────────┐
│ Kubernetes Cluster │
│ │
│ ┌────────────────────────────────────────────────────────────────────────┐ │
│ │ External Secrets Operator │ │
│ │ │ │
│ │ ┌─────────────────┐ ┌──────────────────────────────────────┐ │ │
│ │ │ SecretStore │ │ ExternalSecret │ │ │
│ │ │ │ │ │ │ │
│ │ │ - Vault URL │ │ - refreshInterval: 30s │ │ │
│ │ │ - Auth Method │────────►│ - Maps Vault KV to K8s Secret │ │ │
│ │ │ - KV v2 path │ │ - Adds Reloader annotation │ │ │
│ │ │ │ │ │ │ │
│ │ └─────────────────┘ └──────────────────────────────────────┘ │ │
│ └────────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐ │
│ │ K8s Secret │ │ Stakater │ │ Application │ │
│ │ (app-secrets) │───►│ Reloader │───►│ Pod │ │
│ │ │ │ │ │ │ │
│ │ annotation: │ │ Watches secret │ │ Reads secrets │ │
│ │ reloader.../ │ │ changes and │ │ from env vars │ │
│ │ match: "true" │ │ restarts pods │ │ │ │
│ └──────────────────┘ └──────────────────┘ └──────────────────┘ │
│ ▲ │
│ │ │
│ ┌──────────────────┐ │
│ │ Vault │ │
│ │ Server │ │
│ │ │ │
│ │ KV v2 engine │ │
│ └──────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
Prerequisites#
Complete the common setup steps from the Overview:
- Vault installed, initialized, and unsealed
- Vault CLI configured
- KV v2 secrets engine enabled
- Test secrets written to
secret/myapp - Read policy (
myapp-read) created - Stakater Reloader installed
Additional requirements:
- External Secrets Operator installed
Install External Secrets Operator#
helm repo add external-secrets https://charts.external-secrets.io
helm repo update
helm install external-secrets external-secrets/external-secrets \
--namespace external-secrets \
--create-namespace \
--wait
Verify installation:
kubectl get pods -n external-secrets
Option 1: Token Authentication#
This section covers setting up ESO with Vault token authentication. This method stores a Vault token in a Kubernetes Secret.
Token: Step 1 - Create Vault Policy#
vault policy write myapp-read - <<EOF
path "secret/data/myapp" {
capabilities = ["read"]
}
path "secret/metadata/myapp" {
capabilities = ["read"]
}
EOF
Token: Step 2 - Write Secrets#
vault kv put secret/myapp \
username="admin-user" \
password="super-secret-password"
Token: Step 3 - Create Vault Token#
Create a token with the read policy:
vault token create \
-policy=myapp-read \
-period=24h \
-display-name=eso-token
Important: Save the token value from the output. You'll need it in the next step.
Example output:
Key Value
--- -----
token hvs.CAESI...
token_accessor abc123...
token_duration 24h
token_renewable true
token_policies ["default", "myapp-read"]
Token: Step 4 - Create Application Namespace and Token Secret#
# Create namespace
kubectl create namespace vault-eso-test
# Create token secret for ESO
# Replace <VAULT_TOKEN> with the token from Step 3
kubectl create secret generic vault-token -n vault-eso-test \
--from-literal=token="<VAULT_TOKEN>"
Token: Step 5 - Create SecretStore#
Create secret-store.yaml:
apiVersion: external-secrets.io/v1
kind: SecretStore
metadata:
name: vault-secret-store
namespace: vault-eso-test
spec:
provider:
vault:
server: "http://vault.vault.svc.cluster.local:8200"
path: "secret"
version: "v2"
auth:
tokenSecretRef:
name: vault-token
key: token
Apply and verify:
kubectl apply -f secret-store.yaml
kubectl get secretstore -n vault-eso-test
Should show STATUS: Valid and READY: True.
Now proceed to Create ExternalSecret section.
Option 2: Kubernetes Authentication#
This section covers setting up ESO with Vault Kubernetes authentication. This method uses Kubernetes ServiceAccount tokens to authenticate with Vault - no static credentials required.
K8s Auth: Step 1 - Create Vault Policy#
vault policy write myapp-read - <<EOF
path "secret/data/myapp" {
capabilities = ["read"]
}
path "secret/metadata/myapp" {
capabilities = ["read"]
}
EOF
K8s Auth: Step 2 - Enable and Configure Kubernetes Auth#
# Enable Kubernetes auth method (skip if already enabled)
vault auth enable kubernetes
# Configure Kubernetes auth
vault write auth/kubernetes/config \
kubernetes_host="https://kubernetes.default.svc.cluster.local"
K8s Auth: Step 3 - Create Vault Role#
vault write auth/kubernetes/role/eso-role \
bound_service_account_names=vault-eso-sa \
bound_service_account_namespaces=vault-eso-test \
policies=myapp-read \
ttl=1h
K8s Auth: Step 4 - Write Secrets#
vault kv put secret/myapp \
username="admin-user" \
password="super-secret-password"
K8s Auth: Step 5 - Create Application Namespace and ServiceAccount#
# Create namespace
kubectl create namespace vault-eso-test
# Create service account for ESO to use
kubectl create serviceaccount vault-eso-sa -n vault-eso-test
K8s Auth: Step 6 - Create SecretStore#
Create secret-store.yaml:
apiVersion: external-secrets.io/v1
kind: SecretStore
metadata:
name: vault-secret-store
namespace: vault-eso-test
spec:
provider:
vault:
server: "http://vault.vault.svc.cluster.local:8200"
path: "secret"
version: "v2"
auth:
kubernetes:
mountPath: "kubernetes"
role: "eso-role"
serviceAccountRef:
name: vault-eso-sa
Apply and verify:
kubectl apply -f secret-store.yaml
kubectl get secretstore -n vault-eso-test
Should show STATUS: Valid and READY: True.
Now proceed to Create ExternalSecret section.
Common Configuration#
The following sections apply to both authentication methods.
Create ExternalSecret#
Create external-secret.yaml:
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: app-secrets
namespace: vault-eso-test
spec:
refreshInterval: 30s
secretStoreRef:
name: vault-secret-store
kind: SecretStore
target:
name: app-secrets
creationPolicy: Owner
template:
metadata:
annotations:
# Reloader annotation - triggers restart on workloads with search: "true"
reloader.stakater.com/match: "true"
data:
- secretKey: username
remoteRef:
key: secret/data/myapp
property: username
- secretKey: password
remoteRef:
key: secret/data/myapp
property: password
Apply and verify:
kubectl apply -f external-secret.yaml
kubectl get externalsecret -n vault-eso-test
Should show STATUS: SecretSynced and READY: True.
Deploy Application#
Create deployment.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: vault-eso-test-app
namespace: vault-eso-test
annotations:
reloader.stakater.com/search: "true"
spec:
replicas: 1
selector:
matchLabels:
app: vault-eso-test-app
template:
metadata:
labels:
app: vault-eso-test-app
spec:
containers:
- name: app
image: busybox:latest
command:
- "sh"
- "-c"
- |
while true; do
echo "Username: $APP_USERNAME"
echo "Password: $APP_PASSWORD"
sleep 30
done
env:
- name: APP_USERNAME
valueFrom:
secretKeyRef:
name: app-secrets
key: username
- name: APP_PASSWORD
valueFrom:
secretKeyRef:
name: app-secrets
key: password
Apply:
kubectl apply -f deployment.yaml
Verify the Setup#
# SecretStore
kubectl get secretstore -n vault-eso-test
# ExternalSecret
kubectl get externalsecret -n vault-eso-test
# Secret (created by ESO)
kubectl get secret app-secrets -n vault-eso-test
# Pod
kubectl get pods -n vault-eso-test
# Verify secret contents
kubectl get secret app-secrets -n vault-eso-test -o jsonpath='{.data.password}' | base64 -d
# Check app logs
kubectl logs -n vault-eso-test -l app=vault-eso-test-app
Test Secret Rotation#
Update Secret in Vault#
vault kv put secret/myapp \
username="admin-user" \
password="new-rotated-password"
Wait and Verify#
Wait 30–60 seconds for ESO refresh and Reloader restart:
# Check secret was updated
kubectl get secret app-secrets -n vault-eso-test -o jsonpath='{.data.password}' | base64 -d
# Check pod was restarted (new pod name)
kubectl get pods -n vault-eso-test -l app=vault-eso-test-app
# Check app logs show new password
kubectl logs -n vault-eso-test -l app=vault-eso-test-app --tail=5
Configuration Reference#
SecretStore Configuration#
Token Authentication#
| Field | Description |
|---|---|
server |
Vault server URL |
path |
KV secrets engine mount path (e.g., secret) |
version |
KV engine version (v2) |
auth.tokenSecretRef.name |
Name of K8s Secret containing Vault token |
auth.tokenSecretRef.key |
Key in the Secret containing the token value |
Kubernetes Authentication#
| Field | Description |
|---|---|
server |
Vault server URL |
path |
KV secrets engine mount path (e.g., secret) |
version |
KV engine version (v2) |
auth.kubernetes.mountPath |
Vault auth mount path (e.g., kubernetes) |
auth.kubernetes.role |
Vault role name bound to the ServiceAccount |
auth.kubernetes.serviceAccountRef.name |
Kubernetes ServiceAccount name |
ExternalSecret Configuration#
| Field | Description |
|---|---|
refreshInterval |
How often ESO syncs secrets from Vault (e.g., 30s) |
secretStoreRef |
Reference to the SecretStore |
target.name |
Name of the K8s Secret to create |
target.template.metadata.annotations |
Annotations to add to the created Secret |
data[].secretKey |
Key name in the K8s Secret |
data[].remoteRef.key |
Path in Vault (KV v2: secret/data/<path>) |
data[].remoteRef.property |
Specific field within the Vault secret |
Reloader Annotations#
| Resource | Annotation |
|---|---|
| Deployment | reloader.stakater.com/search: "true" |
| Secret (via ExternalSecret template) | reloader.stakater.com/match: "true" |
Comparison: Token vs Kubernetes Authentication#
| Aspect | Token | Kubernetes Auth |
|---|---|---|
| Credentials | Static token stored in K8s Secret | Dynamic tokens from ServiceAccount |
| Security | Token must be manually rotated | No static secrets, tokens auto-rotate |
| Setup Complexity | Simpler - just create a token | Requires Vault K8s auth configuration |
| Vault Config | Policy + token | Policy + auth method + role |
| Best For | Development, simple setups | Production, security-conscious environments |