Skip to content

How to Use Reloader with Vault Secrets Operator Pattern#

This guide explains how to set up HashiCorp Vault with the Vault Secrets Operator (VSO), combined with Stakater Reloader for automatic pod restarts when secrets change.

Authentication Options#

VSO supports multiple authentication methods with Vault:

Method Description Use Case
Kubernetes Auth Uses Kubernetes ServiceAccount tokens for authentication Recommended for production, no static credentials
AppRole Uses a RoleID and SecretID pair Good for non-Kubernetes clients or cross-cluster setups

Choose the authentication method that best fits your security requirements and proceed to the corresponding section.

Overview#

┌─────────────────────────────────────────────────────────────────────────────┐
│                           Kubernetes Cluster                                 │
│                                                                              │
│  ┌────────────────────────────────────────────────────────────────────────┐ │
│  │                    Vault Secrets Operator                               │ │
│  │                                                                         │ │
│  │  ┌─────────────────┐  ┌────────────┐  ┌────────────────────────────┐  │ │
│  │  │ VaultConnection │  │ VaultAuth  │  │ VaultStaticSecret          │  │ │
│  │  │                 │  │            │  │                            │  │ │
│  │  │ - Vault address │─►│ - K8s Auth │─►│ - mount: secret            │  │ │
│  │  │                 │  │   or       │  │ - path: myapp              │  │ │
│  │  │                 │  │ - AppRole  │  │ - refreshAfter: 30s        │  │ │
│  │  │                 │  │            │  │ - 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   │                                                       │
│  └──────────────────┘                                                       │
└─────────────────────────────────────────────────────────────────────────────┘

Note: VSO has a built-in rolloutRestartTargets field on VaultStaticSecret that can trigger workload restarts natively. However, using Stakater Reloader provides a uniform restart mechanism across all secret operators (ESO, VSO, CSI), which is valuable when running multiple patterns in the same cluster.

Prerequisites#

Complete the common setup steps from the Overview:

  • Vault installed and unsealed
  • Vault CLI configured
  • KV v2 secrets engine enabled
  • Test secrets written to secret/myapp
  • Stakater Reloader installed

Additional requirements:

  • Vault Secrets Operator installed

Install Vault Secrets Operator#

helm repo add hashicorp https://helm.releases.hashicorp.com
helm repo update
helm install vault-secrets-operator hashicorp/vault-secrets-operator \
  --namespace vault-secrets-operator-system \
  --create-namespace \
  --wait

Verify installation:

kubectl get pods -n vault-secrets-operator-system

Option 1: Kubernetes Authentication#

This section covers setting up VSO with Vault Kubernetes authentication. This method uses Kubernetes ServiceAccount tokens to authenticate with Vault.

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/vso-role \
  bound_service_account_names=vault-vso-sa \
  bound_service_account_namespaces=vault-vso-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-vso-test

# Create service account
kubectl create serviceaccount vault-vso-sa -n vault-vso-test

K8s Auth: Step 6 - Create VaultConnection#

Create vault-connection.yaml:

apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultConnection
metadata:
  name: vault-connection
  namespace: vault-vso-test
spec:
  address: "http://vault.vault.svc.cluster.local:8200"

Apply:

kubectl apply -f vault-connection.yaml

K8s Auth: Step 7 - Create VaultAuth#

Create vault-auth.yaml:

apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultAuth
metadata:
  name: vault-auth
  namespace: vault-vso-test
spec:
  vaultConnectionRef: vault-connection
  method: kubernetes
  mount: kubernetes
  kubernetes:
    role: vso-role
    serviceAccount: vault-vso-sa

Apply:

kubectl apply -f vault-auth.yaml

K8s Auth: Step 8 - Create VaultStaticSecret#

Create vault-static-secret.yaml:

apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultStaticSecret
metadata:
  name: app-secrets
  namespace: vault-vso-test
spec:
  vaultAuthRef: vault-auth
  mount: secret
  type: kv-v2
  path: myapp
  refreshAfter: 30s
  destination:
    name: app-secrets
    create: true
    annotations:
      reloader.stakater.com/match: "true"

Apply:

kubectl apply -f vault-static-secret.yaml

Now proceed to Deploy Application section.


Option 2: AppRole Authentication#

This section covers setting up VSO with Vault AppRole authentication. This method uses a RoleID and SecretID pair.

AppRole: Step 1 - Create Vault Policy#

vault policy write myapp-read - <<EOF
path "secret/data/myapp" {
  capabilities = ["read"]
}
path "secret/metadata/myapp" {
  capabilities = ["read"]
}
EOF

AppRole: Step 2 - Enable and Configure AppRole Auth#

# Enable AppRole auth method (skip if already enabled)
vault auth enable approle

# Create AppRole role
vault write auth/approle/role/vso-role \
  secret_id_ttl=0 \
  token_policies=myapp-read \
  token_ttl=1h \
  token_max_ttl=4h

AppRole: Step 3 - Get RoleID and SecretID#

# Get RoleID
ROLE_ID=$(vault read -field=role_id auth/approle/role/vso-role/role-id)
echo "RoleID: $ROLE_ID"

# Generate SecretID
SECRET_ID=$(vault write -field=secret_id -f auth/approle/role/vso-role/secret-id)
echo "SecretID: $SECRET_ID"

Important: Save both values. You'll need them in Step 5.

AppRole: Step 4 - Write Secrets#

vault kv put secret/myapp \
  username="admin-user" \
  password="super-secret-password"

AppRole: Step 5 - Create Application Namespace and AppRole Secret#

# Create namespace
kubectl create namespace vault-vso-test

# Create service account (required by the deployment)
kubectl create serviceaccount vault-vso-sa -n vault-vso-test

# Create Kubernetes Secret with AppRole SecretID
# The secret must have a key named "id" (required by VSO)
# Replace <SECRET_ID> with the SecretID from Step 3
kubectl create secret generic vault-approle-secret -n vault-vso-test \
  --from-literal=id="<SECRET_ID>"

AppRole: Step 6 - Create VaultConnection#

Create vault-connection.yaml:

apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultConnection
metadata:
  name: vault-connection
  namespace: vault-vso-test
spec:
  address: "http://vault.vault.svc.cluster.local:8200"

Apply:

kubectl apply -f vault-connection.yaml

AppRole: Step 7 - Create VaultAuth#

Create vault-auth.yaml:

apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultAuth
metadata:
  name: vault-auth
  namespace: vault-vso-test
spec:
  vaultConnectionRef: vault-connection
  method: appRole
  mount: approle
  appRole:
    roleId: <ROLE_ID>
    secretRef: vault-approle-secret

Note: Replace <ROLE_ID> with the RoleID from Step 3. The secretRef is the name of the K8s Secret created in Step 5, which must contain a key named id with the SecretID value.

Apply:

kubectl apply -f vault-auth.yaml

AppRole: Step 8 - Create VaultStaticSecret#

Create vault-static-secret.yaml:

apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultStaticSecret
metadata:
  name: app-secrets
  namespace: vault-vso-test
spec:
  vaultAuthRef: vault-auth
  mount: secret
  type: kv-v2
  path: myapp
  refreshAfter: 30s
  destination:
    name: app-secrets
    create: true
    annotations:
      reloader.stakater.com/match: "true"

Apply:

kubectl apply -f vault-static-secret.yaml

Now proceed to Deploy Application section.


Common Configuration#

The following sections apply to both authentication methods.

Deploy Application#

Create deployment.yaml:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: vault-vso-test-app
  namespace: vault-vso-test
  annotations:
    reloader.stakater.com/search: "true"
spec:
  replicas: 1
  selector:
    matchLabels:
      app: vault-vso-test-app
  template:
    metadata:
      labels:
        app: vault-vso-test-app
    spec:
      serviceAccountName: vault-vso-sa
      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#

# VaultConnection
kubectl get vaultconnection -n vault-vso-test

# VaultAuth
kubectl get vaultauth -n vault-vso-test

# VaultStaticSecret
kubectl get vaultstaticsecret -n vault-vso-test

# Secret (created by VSO)
kubectl get secret app-secrets -n vault-vso-test

# Pod
kubectl get pods -n vault-vso-test

# Verify secret contents
kubectl get secret app-secrets -n vault-vso-test -o jsonpath='{.data.password}' | base64 -d

# Check app logs
kubectl logs -n vault-vso-test -l app=vault-vso-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 VSO refresh and Reloader restart:

# Check secret was updated
kubectl get secret app-secrets -n vault-vso-test -o jsonpath='{.data.password}' | base64 -d

# Check pod was restarted (new pod name)
kubectl get pods -n vault-vso-test -l app=vault-vso-test-app

# Check app logs show new password
kubectl logs -n vault-vso-test -l app=vault-vso-test-app --tail=5

Configuration Reference#

VaultConnection Configuration#

Field Description
address Vault server URL (e.g., http://vault.vault.svc.cluster.local:8200)
caCertSecretRef (Optional) Reference to K8s Secret containing Vault CA certificate
tlsServerName (Optional) TLS server name for certificate verification
skipTLSVerify (Optional) Skip TLS verification (not recommended for production)

VaultAuth Configuration#

Kubernetes Authentication#

Field Description
vaultConnectionRef Reference to the VaultConnection resource
method Auth method: kubernetes
mount Auth mount path in Vault (e.g., kubernetes)
kubernetes.role Vault role name
kubernetes.serviceAccount Kubernetes ServiceAccount name

AppRole Authentication#

Field Description
vaultConnectionRef Reference to the VaultConnection resource
method Auth method: appRole
mount Auth mount path in Vault (e.g., approle)
appRole.roleId AppRole RoleID
appRole.secretRef Name of K8s Secret containing SecretID (must have a key named id)

VaultStaticSecret Configuration#

Field Description
vaultAuthRef Reference to the VaultAuth resource
mount Secrets engine mount path (e.g., secret)
type Secrets engine type (kv-v2 or kv-v1)
path Secret path within the mount (e.g., myapp)
refreshAfter How often VSO syncs secrets from Vault (e.g., 30s)
destination.name Name of the K8s Secret to create
destination.create Whether to create the Secret if it doesn't exist
destination.annotations Annotations to add to the created Secret

Reloader Annotations#

Resource Annotation
Deployment reloader.stakater.com/search: "true"
Secret (via VaultStaticSecret destination) reloader.stakater.com/match: "true"

Comparison: Kubernetes Auth vs AppRole#

Aspect Kubernetes Auth AppRole
Credentials Dynamic tokens from ServiceAccount Static RoleID + rotating SecretID
Security No static secrets, tokens auto-rotate SecretID can be rotated, RoleID is static
Setup Complexity Requires Vault K8s auth configuration Requires AppRole setup + secret management
Vault Config Policy + K8s auth role Policy + AppRole role
Best For In-cluster workloads, production Cross-cluster setups, CI/CD pipelines