HashiCorp Vault — Metadata & Versioning
KV v2 provides rich metadata and version control for every secret. This page covers how to work with versions, custom metadata, delete/destroy operations, and version lifecycle management.
Version Lifecycle
Write v1 → Write v2 → Write v3 → Soft-Delete v2 → Undelete v2 → Destroy v1
│ │ │ │ │ │
▼ ▼ ▼ ▼ ▼ ▼
Active Active Active v2 marked as v2 restored v1 data
(current) deleted (data (accessible permanently
preserved) again) gone
View All Versions
# Write multiple versions
vault kv put secret/app password="v1-pass"
vault kv put secret/app password="v2-pass"
vault kv put secret/app password="v3-pass"
# View metadata with version info
vault kv metadata get secret/app
Expected output:
======= Metadata =======
Key Value
--- -----
cas_required false
created_time 2024-01-15T10:00:00.000000Z
current_version 3
custom_metadata <nil>
delete_version_after 0s
max_versions 0
oldest_version 0
updated_time 2024-01-15T10:05:00.000000Z
====== Version 1 ======
Key Value
--- -----
created_time 2024-01-15T10:00:00.000000Z
deletion_time n/a
destroyed false
====== Version 2 ======
Key Value
--- -----
created_time 2024-01-15T10:02:00.000000Z
deletion_time n/a
destroyed false
====== Version 3 ======
Key Value
--- -----
created_time 2024-01-15T10:05:00.000000Z
deletion_time n/a
destroyed false
Custom Metadata
Custom metadata is attached to the secret (not a specific version) and is useful for tagging, ownership, and compliance:
# Set custom metadata
vault kv metadata put \
-custom-metadata=owner="sre-team" \
-custom-metadata=environment="production" \
-custom-metadata=compliance="pci-dss" \
-custom-metadata=last-rotated="2024-01-15" \
secret/payments/stripe-key
# View custom metadata
vault kv metadata get secret/payments/stripe-key
# Update a single custom metadata field
vault kv metadata patch \
-custom-metadata=last-rotated="2024-02-15" \
secret/payments/stripe-key
API equivalent:
curl --header "X-Vault-Token: $VAULT_TOKEN" \
--request POST \
--data '{
"custom_metadata": {
"owner": "sre-team",
"environment": "production"
}
}' \
"$VAULT_ADDR/v1/secret/metadata/payments/stripe-key"
Version Limits
Max Versions
Control how many versions are retained:
# Set max versions for a specific secret
vault kv metadata put -max-versions=5 secret/app
# Set default for the entire mount
vault secrets tune -options=max_versions=10 secret/
# When version 6 is written, version 1 is automatically deleted
Delete Version After (Auto-Expiry)
Automatically soft-delete versions after a time period:
# Versions auto-expire after 30 days
vault kv metadata put -delete-version-after=720h secret/app
# Set at mount level
vault secrets tune -options=delete_version_after=720h secret/
Version States
A version can be in one of three states:
| State | Description | Can Read? | Can Recover? |
|---|---|---|---|
| Active | Normal, accessible version | ✅ | N/A |
| Soft-deleted | Marked as deleted, data preserved | ❌ | ✅ undelete |
| Destroyed | Data permanently removed | ❌ | ❌ |
# Active → Soft-deleted
vault kv delete -versions=2 secret/app
# Soft-deleted → Active
vault kv undelete -versions=2 secret/app
# Active → Destroyed (PERMANENT)
vault kv destroy -versions=1 secret/app
Patch Operations
Update specific fields without replacing the entire secret:
# Write initial secret
vault kv put secret/app host="db.local" port="5432" user="admin" pass="old"
# Patch only the password (other fields preserved)
vault kv patch secret/app pass="new-password"
# Verify — all fields present, pass updated
vault kv get secret/app
Expected output:
====== Data ======
Key Value
--- -----
host db.local
pass new-password
port 5432
user admin
API equivalent:
curl --header "X-Vault-Token: $VAULT_TOKEN" \
--header "Content-Type: application/merge-patch+json" \
--request PATCH \
--data '{"data": {"pass": "new-password"}}' \
"$VAULT_ADDR/v1/secret/data/app"
Comparing Versions
# Read version 1
vault kv get -format=json -version=1 secret/app | jq '.data.data'
# Read version 2
vault kv get -format=json -version=2 secret/app | jq '.data.data'
# Quick diff with diff command
diff <(vault kv get -format=json -version=1 secret/app | jq -S '.data.data') \
<(vault kv get -format=json -version=2 secret/app | jq -S '.data.data')
Rollback to a Previous Version
Vault doesn’t have a built-in rollback command, but you can re-write an old version’s data:
# Get the data from version 1
OLD_DATA=$(vault kv get -format=json -version=1 secret/app | jq -c '.data.data')
# Write it as a new version
echo "$OLD_DATA" | vault kv put secret/app -
# Or use the API
curl --header "X-Vault-Token: $VAULT_TOKEN" \
--request POST \
--data "{\"data\": $OLD_DATA}" \
"$VAULT_ADDR/v1/secret/data/app"
Quick Reference
| Command | Description |
|---|---|
vault kv metadata get secret/path |
View all version metadata |
vault kv get -version=N secret/path |
Read specific version |
vault kv metadata put -max-versions=N secret/path |
Set max versions |
vault kv metadata put -delete-version-after=Nh secret/path |
Auto-expire |
vault kv metadata put -custom-metadata=k=v secret/path |
Custom metadata |
vault kv metadata patch -custom-metadata=k=v secret/path |
Update metadata |
vault kv patch secret/path key=value |
Update single field |
vault kv delete -versions=N secret/path |
Soft-delete |
vault kv undelete -versions=N secret/path |
Restore |
vault kv destroy -versions=N secret/path |
Permanent destroy |
vault kv metadata delete secret/path |
Delete everything |
Next: Practice Exercises