Vault Integration#
Support level This integration is validated and supported in Reloader Enterprise. Community usage is possible, but without continuous validation or SLA
This guide shows how to automatically restart Kubernetes workloads when HashiCorp Vault secrets change using Stakater Reloader.
Integration Patterns#
| Pattern | How Secrets Arrive | Rotation | Reloader Compatibility | Guide |
|---|---|---|---|---|
| External Secrets Operator | ESO syncs to K8s Secret | ESO refresh interval | Best fit | ESO Guide |
| Vault Secrets Operator | VSO syncs to K8s Secret | VSO refresh interval | Best fit | VSO Guide |
| CSI Driver | CSI mounts files + syncs to K8s Secret | CSI rotation interval | Works with secretObjects |
CSI Guide |
| CSI Driver (File-Based) | CSI mounts secrets as files only | CSI rotation interval | Watches SecretProviderClassPodStatus | CSI File Guide |
Architecture Overview#
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Vault + Reloader Architecture β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β Vault Server (vault namespace): β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β HashiCorp Vault β β
β β ββββββββββββββββββββ ββββββββββββββββββββ ββββββββββββββββββββ β β
β β β KV v2 Engine β β Kubernetes Auth β β AppRole Auth β β β
β β β secret/myapp β β (k8s SA tokens) β β (RoleID/SecretID)β β β
β β ββββββββββββββββββββ ββββββββββββββββββββ ββββββββββββββββββββ β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β β
β βΌ β
β Application Namespace: β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β Secret Sync (ESO / VSO / CSI) β β
β β ββββββββββββββββββββ ββββββββββββββββββββ β β
β β β Operator CRDs βββββΊβ K8s Secret β β β
β β β (SecretStore, β β (app-secrets) β β β
β β β VaultAuth, etc.)β β match: "true" β β β
β β ββββββββββββββββββββ ββββββββββββββββββββ β β
β β β β β
β β βΌ β β
β β ββββββββββββββββββββ ββββββββββββββββββββ β β
β β β Stakater ReloaderβββββΊβ Application Pod β β β
β β β Detects change β β Rolling restart β β β
β β ββββββββββββββββββββ ββββββββββββββββββββ β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Prerequisites#
- Kubernetes cluster (v1.19+)
- Helm v3+
- HashiCorp Vault (OSS or Enterprise)
- Vault CLI installed locally
- Stakater Reloader installed
kubectlconfigured with cluster access
Install Stakater Reloader#
helm repo add stakater https://stakater.github.io/stakater-charts
helm install reloader stakater/reloader --namespace reloader --create-namespace
Common Setup Steps#
Step 1: Install Vault#
# Add HashiCorp Helm repo
helm repo add hashicorp https://helm.releases.hashicorp.com
helm repo update
# Install Vault
helm install vault hashicorp/vault -n vault --create-namespace \
--set "server.dataStorage.size=1Gi" \
--set "injector.enabled=false"
Note: The injector is disabled because this guide uses ESO, VSO, or CSI to sync secrets. Enable it if you also need Vault Agent sidecar injection.
Wait for the pod to start (it will be Running but not Ready until unsealed):
kubectl wait --for=jsonpath='{.status.phase}'=Running pod/vault-0 -n vault --timeout=180s
Step 2: Initialize and Unseal Vault#
# Initialize Vault (using 1 key share for simplicity; use more in production)
kubectl exec -n vault vault-0 -- vault operator init \
-key-shares=1 \
-key-threshold=1 \
-format=json > vault-init.json
Important: Save vault-init.json securely. It contains the unseal key and root token.
# Extract unseal key and root token
VAULT_UNSEAL_KEY=$(cat vault-init.json | jq -r '.unseal_keys_b64[0]')
VAULT_ROOT_TOKEN=$(cat vault-init.json | jq -r '.root_token')
# Unseal Vault
kubectl exec -n vault vault-0 -- vault operator unseal "$VAULT_UNSEAL_KEY"
Vault should now show Sealed: false. Wait for the pod to become Ready:
kubectl wait --for=condition=ready pod/vault-0 -n vault --timeout=30s
Step 3: Configure Vault CLI#
# Port-forward for local CLI access
kubectl port-forward -n vault svc/vault 8200:8200 &
# Configure CLI (use the root token from Step 2)
export VAULT_ADDR="http://127.0.0.1:8200"
export VAULT_TOKEN="$VAULT_ROOT_TOKEN"
Step 4: Enable KV v2 Secrets Engine#
vault secrets enable -path=secret kv-v2
Verify:
vault secrets list
You should see secret/ listed as kv version 2.
Step 5: Write Test Secrets#
vault kv put secret/myapp \
username="admin-user" \
password="super-secret-password"
Verify:
vault kv get secret/myapp
Step 6: Create Vault Read Policy#
vault policy write myapp-read - <<EOF
path "secret/data/myapp" {
capabilities = ["read"]
}
path "secret/metadata/myapp" {
capabilities = ["read"]
}
EOF
Step 7: Enable Kubernetes Auth#
# Enable Kubernetes auth method
vault auth enable kubernetes
# Configure Kubernetes auth
vault write auth/kubernetes/config \
kubernetes_host="https://kubernetes.default.svc.cluster.local"
Step 8: Enable AppRole Auth (Optional)#
Only required for the VSO AppRole pattern.
vault auth enable approle
How Reloader Works#
- Secret provider updates K8s Secret - ESO/VSO/CSI syncs secrets from Vault
- Reloader detects change - Watches for Secret changes via Kubernetes API
- Pod restart triggered - Rolling restart of pods referencing the changed Secret
Reloader Annotations#
On Deployment (for K8s Secret-based patterns: ESO, VSO, CSI with secretObjects):
metadata:
annotations:
reloader.stakater.com/search: "true"
On Secret (must be annotation, not label):
metadata:
annotations:
reloader.stakater.com/match: "true"
On Deployment (for CSI file-based pattern only):
metadata:
annotations:
secretproviderclass.reloader.stakater.com/reload: "<secretproviderclass-name>"
Pattern-Specific Guides#
- External Secrets Operator Pattern - Token or Kubernetes auth
- Vault Secrets Operator Pattern - Kubernetes auth or AppRole
- CSI Driver Pattern - Kubernetes auth
- CSI Driver File-Based Pattern - Kubernetes auth, no K8s Secret
Using External Vault (Multi-Cluster Setup)#
Vault does not need to run inside Kubernetes. A single external Vault instance can serve multiple Kubernetes clusters. This is a common production pattern for centralized secrets management.
Requirements#
- Network connectivity - Pods must be able to reach the external Vault URL
- TLS certificates - Production Vault should use TLS; clusters need the CA certificate
- Per-cluster Kubernetes authentication configuration - Each cluster's JWT tokens are unique
Authentication Methods with External Vault#
| Auth Method | Works with External Vault? | Additional Configuration |
|---|---|---|
| Token | Yes | None - just update Vault URL |
| AppRole | Yes | None - just update Vault URL |
| Kubernetes | Yes | Requires per-cluster auth mount configuration |
Configuring Kubernetes Auth for External Vault#
When Vault runs outside the cluster, it cannot automatically discover the Kubernetes API. You must provide:
# Create a service account for Vault to validate tokens
kubectl create serviceaccount vault-auth -n kube-system
# Grant token review permissions
kubectl create clusterrolebinding vault-auth-delegator \
--clusterrole=system:auth-delegator \
--serviceaccount=kube-system:vault-auth
# Get the service account token (for K8s 1.24+, create a secret)
kubectl apply -f - <<EOF
apiVersion: v1
kind: Secret
metadata:
name: vault-auth-token
namespace: kube-system
annotations:
kubernetes.io/service-account.name: vault-auth
type: kubernetes.io/service-account-token
EOF
# Extract values for Vault configuration
TOKEN_REVIEWER_JWT=$(kubectl get secret vault-auth-token -n kube-system -o jsonpath='{.data.token}' | base64 -d)
KUBE_CA_CERT=$(kubectl config view --raw --minify --flatten -o jsonpath='{.clusters[0].cluster.certificate-authority-data}' | base64 -d)
KUBE_HOST=$(kubectl config view --raw --minify --flatten -o jsonpath='{.clusters[0].cluster.server}')
Configure Vault with a separate auth mount per cluster:
# Enable Kubernetes auth for this cluster
vault auth enable -path=kubernetes-cluster-a kubernetes
# Configure with cluster-specific values
vault write auth/kubernetes-cluster-a/config \
kubernetes_host="$KUBE_HOST" \
kubernetes_ca_cert="$KUBE_CA_CERT" \
token_reviewer_jwt="$TOKEN_REVIEWER_JWT"
# Create role (same as before)
vault write auth/kubernetes-cluster-a/role/myapp-role \
bound_service_account_names=myapp-sa \
bound_service_account_namespaces=production \
policies=myapp-read \
ttl=1h
Manifest Changes for External Vault#
Update the Vault address in your manifests:
ESO SecretStore:
spec:
provider:
vault:
server: "https://vault.example.com:8200"
caBundle: "<base64-encoded-ca-cert>" # For TLS
path: secret
auth:
kubernetes:
mountPath: kubernetes-cluster-a # Cluster-specific mount
role: myapp-role
VSO VaultConnection:
spec:
address: "https://vault.example.com:8200"
caCertSecretRef:
name: vault-ca-cert
key: ca.crt
VSO VaultAuth (with external Vault):
spec:
method: kubernetes
mount: kubernetes-cluster-a # Cluster-specific mount
kubernetes:
role: myapp-role
serviceAccount: myapp-sa
CSI SecretProviderClass:
parameters:
vaultAddress: "https://vault.example.com:8200"
vaultCACertPath: "/vault/tls/ca.crt" # Or use vaultSkipTLSVerify for testing
roleName: myapp-role
Multi-Cluster Architecture#
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β External Vault Server β
β (https://vault.example.com:8200) β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β ββββββββββββββββββββ ββββββββββββββββββββ ββββββββββββββββββββ β β
β β β KV v2 Engine β β kubernetes- β β kubernetes- β β β
β β β secret/myapp β β cluster-a/ β β cluster-b/ β β β
β β β β β (auth mount) β β (auth mount) β β β
β β ββββββββββββββββββββ ββββββββββββββββββββ ββββββββββββββββββββ β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β β β
βΌ βΌ βΌ
ββββββββββββββββββββ ββββββββββββββββββββ ββββββββββββββββββββ
β Cluster A β β Cluster B β β Cluster C β
β (Production) β β (Staging) β β (Dev) β
β β β β β β
β ESO / VSO / CSI β β ESO / VSO / CSI β β ESO / VSO / CSI β
β + Reloader β β + Reloader β β + Reloader β
ββββββββββββββββββββ ββββββββββββββββββββ ββββββββββββββββββββ