HashiCorp Vault
Published: 04 Sep. 2026 Last updated: 04 Sep. 2026
Summary
HashiCorp Vault is a widely used identity-based secrets and encryption management system. It stores credentials, certificates, and encryption keys, and issues them dynamically to applications and users. When deployed on Kubernetes, Vault is most commonly configured with Raft integrated storage, in which each Vault Pod keeps a full replica of the data on its own PersistentVolume.
This Application Note discusses how to use CloudCasa to properly protect and restore HashiCorp Vault running in containers under Kubernetes.
CloudCasa has been tested for this application note with a 3 node Vault cluster using Raft integrated storage v2.0.4, deployed with the official HashiCorp Vault Helm chart v0.34.1, and configured for auto-unseal using the Transit secrets engine of a second standalone Vault instance. The information herein is expected to apply to more recent versions, to different cluster sizes, and to other seal types such as cloud KMS auto-unseal as well, though minor adjustments may be needed.
Configure the CloudCasa Vault Snapshotter
To perform a consistent backup of Vault with Raft storage, the vault operator raft snapshot save command is used to create a snapshot during the backup. CloudCasa requires a running Pod with access to Vault to run the backup hook.
The following instructions can be used to create the Pod that CloudCasa will use.
Note
A Vault snapshot contains the cluster’s data encrypted under its root key, and the root key is in turn wrapped by the seal. A snapshot taken from a cluster using Transit auto-unseal cannot be restored unless the same Transit key is still available. If the Transit Vault is deployed in the same cluster, it must be included in the same CloudCasa backup as the snapshotter.
Note
The commands below that run inside a Vault Pod require an administrative Vault token.
In the examples this is referred to as $VROOT, and can be obtained from the output saved when the cluster was initialized: VROOT=$(jq -r '.root_token' vault-init.json)
Create a snapshot policy in Vault. This policy grants read access to the snapshot endpoint:
kubectl -n vault exec -i vault-0 -- sh -c " export VAULT_TOKEN=$VROOT vault policy write snapshot - <<'EOP' path \"sys/storage/raft/snapshot\" { capabilities = [\"read\"] } EOP "
Create the namespace, service account, and Kubernetes authentication:
Create the
namespace:kubectl create namespace cc-vault-backup
Create the
serviceaccount:kubectl -n cc-vault-backup create serviceaccount cc-vault-snapshot
Check if a
clusterrolebindingalso needs to be created. Vault validates the snapshotter’s service account token by calling the Kubernetes TokenReview API, so Vault’s own service account requires permission to do so:kubectl auth can-i create tokenreviews --as=system:serviceaccount:vault:vault
If it returns
no, run the following command to create it:kubectl create clusterrolebinding vault-token-review --clusterrole=system:auth-delegator --serviceaccount=vault:vault
Enable and configure the Kubernetes authentication method:
kubectl -n vault exec -i vault-0 -- sh -c " export VAULT_TOKEN=$VROOT vault auth enable kubernetes vault write auth/kubernetes/config \ kubernetes_host=\"https://\$KUBERNETES_PORT_443_TCP_ADDR:443\" "
Create the role that binds the service account to the snapshot policy:
kubectl -n vault exec -i vault-0 -- sh -c " export VAULT_TOKEN=$VROOT vault write auth/kubernetes/role/snapshot \ bound_service_account_names=cc-vault-snapshot \ bound_service_account_namespaces=cc-vault-backup \ policies=snapshot \ ttl=20m " .. note:: The value of ``bound_service_account_namespaces`` must match the namespace in which the snapshotter is deployed.
Create a PVC for the Vault snapshotter. Replace
STORAGE_CLASS_NAMEwith the name of your storage class:apiVersion: v1 kind: PersistentVolumeClaim metadata: name: cc-vault-snapshots namespace: cc-vault-backup labels: app: cloudcasa-vault-snapshotter spec: accessModes: [ReadWriteOnce] storageClassName: <STORAGE_CLASS_NAME> resources: requests: storage: 10Gi
Deploy the Vault snapshotter. Adjust the
image,VAULT_ADDR` environment variable, and ``securityContextas necessary for your environment:apiVersion: apps/v1 kind: Deployment metadata: name: cloudcasa-vault-snapshotter namespace: cc-vault-backup labels: app: cloudcasa-vault-snapshotter spec: replicas: 1 strategy: type: Recreate selector: matchLabels: app: cloudcasa-vault-snapshotter template: metadata: labels: app: cloudcasa-vault-snapshotter spec: serviceAccountName: cc-vault-snapshot securityContext: runAsNonRoot: true runAsUser: 100 fsGroup: 1000 containers: - name: snap image: hashicorp/vault:1.17.2 command: ["sleep", "infinity"] env: - name: VAULT_ADDR value: "http://vault-active.vault.svc.cluster.local:8200" resources: requests: {cpu: 50m, memory: 64Mi} limits: {memory: 256Mi} volumeMounts: - name: snap mountPath: /snapshots volumes: - name: snap persistentVolumeClaim: claimName: cc-vault-snapshots
Verify that the snapshot command works from within the Vault snapshotter pod.
Open a shell in the Pod:
kubectl -n cc-vault-backup exec -it deploy/vault-snapshotter -- sh
Create the snapshot:
export VAULT_TOKEN=$(vault write -field=token auth/kubernetes/login role=snapshot jwt=@/var/run/secrets/kubernetes.io/serviceaccount/token) F=/snapshots/vault-cc-test.snap vault operator raft snapshot save "$F"
Verify the snapshot contents:
ls -lh "$F" tar tzf "$F" tar xzf "$F" meta.json -O
The archive should list
meta.json,state.bin, and the checksum files, andmeta.jsonshould report a non-zero Raft index.Delete the test snapshot:
rm -f "$F"
Backup
The following instructions can be used to configure backup of HashiCorp Vault with Raft storage using the cloudcasa-vault-snapshotter.
From the CloudCasa UI, navigate to Configuration → App Hooks and click the “Add app hook” button. We will create an app hook for the
cloudcasa-vault-snapshotter.Specify a name for the App Hook and select “Pre-backup” as the type. Set the pod selector to
app = vault-snapshotterand the container tosnap. Add the following into the command field and then click “Save” to create the App Hook:/bin/sh -c "export VAULT_TOKEN=$(vault write -field=token auth/kubernetes/login role=snapshot jwt=@/var/run/secrets/kubernetes.io/serviceaccount/token) && \ vault operator raft snapshot save /snapshots/vault.snap && \ test -s /snapshots/vault.snap && \ tar tzf /snapshots/vault.snap | grep -q meta.json"
Navigate to Clusters → Overview, select the cluster containing the Vault deployment, and then click “Define backup”.
In the App hooks section of the backup definition, add a pre-backup app hook. Select the
cc-vault-backupnamespace and select the App Hook created previously.Run the backup and confirm the job completes.
Restore
Restoring Vault is a two-part operation. CloudCasa restores the Vault snapshot file and, where applicable, the Transit instance that holds the unseal key. A Vault cluster is then deployed and the snapshot is loaded into it using the Vault CLI.
Note
The Vault snapshot must be restored into a Vault cluster that is already running, unsealed, and has an elected Raft leader. The restore operation is submitted through Raft, so the target cluster must be initialized first even though the credentials created during that initialization are discarded immediately afterwards.
Navigate to Clusters → Recovery Points, find the recovery point you want to restore from, and then click Actions → Restore.
Enable “Select namespaces” and select the
cc-vault-backupnamespace.Also select the namespace of the Vault Transit instance (i.e.
vault-transit) if it was deployed in the same cluster.
Note
The namespace containing the Vault cluster itself (i.e. vault ) does not need to be selected, and its
data-vault-*PersistentVolumes should not be relied upon for recovery. The snapshot file produced by the App Hook is the recoverable copy of the Vault data. Both the snapshotter namespace and the Transit namespace must be protected together, as the snapshot cannot be decrypted without the Transit key.In the Destination section, select the target cluster for the restore.
Run the restore job and verify that the namespaces, including the
cloudcasa-volume-snapshotter, were restored.Unseal the Vault Transit instance if applicable. The Transit instance does not auto-unseal, so it must be unsealed manually after every restart or restore:
UNSEAL=$(jq -r '.unseal_keys_b64[0]' transit-init.json) kubectl -n vault-transit exec vault-transit-0 -- vault operator unseal "$UNSEAL"
Confirm that the Transit key is valid:
TROOT=$(jq -r '.root_token' transit-init.json) kubectl -n vault-transit exec vault-transit-0 -- sh -c "VAULT_TOKEN=$TROOT vault read transit/keys/autounseal"
Copy the Vault snapshot file out of the restored snapshotter Pod:
POD=$(kubectl -n cc-vault-backup get pod -l app=cloudcasa-vault-snapshotter -o name) kubectl -n cc-vault-backup cp ${POD#pod/}:/snapshots/vault.snap ./vault.snap
Deploy Vault with Raft storage following the official guide: Deploy Vault with Raft storage following the official guide
Create temporary credentials by initializing the new cluster. These credentials are only used to submit the restore and become invalid as soon as it completes:
kubectl -n vault exec vault-0 -- vault operator init -recovery-shares=1 -recovery-threshold=1 -format=json > tmp-cred.json TMPROOT=$(jq -r '.root_token' tmp-cred.json)
Wait for quorum. All three Pods should reach the
Runningstate and report as voters, with one leader:kubectl -n vault get pods -w kubectl -n vault exec vault-0 -- sh -c "VAULT_TOKEN=$TMPROOT vault operator raft list-peers"
Copy the Vault snapshot into the leader Pod and restore it:
kubectl -n vault cp ./vault.snap vault-0:/tmp/vault.snap kubectl -n vault exec vault-0 -- sh -c "VAULT_TOKEN=$TMPROOT vault operator raft snapshot restore /tmp/vault.snap"
Switch to the original credentials. The snapshot replaces the target cluster’s keyring with the one from the source cluster, so the temporary root token and recovery keys created previously no longer work, and the source cluster’s credentials must be used from this point onwards:
VROOT=$(jq -r '.root_token' vault-init.json) kubectl -n vault exec vault-0 -- sh -c "VAULT_TOKEN=$VROOT vault token lookup"
Verify the restored data. Confirm that the expected mounts, authentication methods, and secret versions are present:
kubectl -n vault exec vault-0 -- sh -c "VAULT_TOKEN=$VROOT vault secrets list" kubectl -n vault exec vault-0 -- sh -c "VAULT_TOKEN=$VROOT vault auth list" kubectl -n vault exec vault-0 -- sh -c "VAULT_TOKEN=$VROOT vault kv metadata get <path>"