HashiCorp Vault β€” KV Version 1

2 minute read

KV (Key-Value) version 1 is the simplest secrets engine. It stores arbitrary key-value pairs without versioning β€” each write replaces the previous value entirely.


Enable KV v1

# Enable KV v1 at a custom path
vault secrets enable -path=kv-v1 -version=1 kv

# Verify
vault secrets list

Expected output:

Path          Type         Accessor              Description
----          ----         --------              -----------
cubbyhole/    cubbyhole    cubbyhole_abc123      per-token private secret storage
identity/     identity     identity_abc123       identity store
kv-v1/        kv           kv_def456             n/a
secret/       kv           kv_ghi789             key/value secret storage
sys/          system       system_jkl012         system endpoints

Write and Read Secrets

Write a Secret

# Write key-value pairs
vault kv put kv-v1/myapp username="admin" password="s3cur3!"

Expected output:

Success! Data written to: kv-v1/myapp

Read a Secret

vault kv get kv-v1/myapp

Expected output:

====== Data ======
Key         Value
---         -----
password    s3cur3!
username    admin

Read a Single Field

vault kv get -field=password kv-v1/myapp

Expected output:

s3cur3!

KV v1 API Paths

KV v1 uses a flat API path β€” the path you write to is the exact API path:

# CLI
vault kv put kv-v1/myapp key=value

# Equivalent API call
curl --header "X-Vault-Token: $VAULT_TOKEN" \
     --request POST \
     --data '{"username": "admin", "password": "s3cur3!"}' \
     "$VAULT_ADDR/v1/kv-v1/myapp"

# Read via API
curl --header "X-Vault-Token: $VAULT_TOKEN" \
     "$VAULT_ADDR/v1/kv-v1/myapp" | jq

API response:

{
  "request_id": "abc123",
  "data": {
    "username": "admin",
    "password": "s3cur3!"
  }
}

πŸ’‘ Note: KV v1 API paths are simpler than KV v2. There’s no /data/ or /metadata/ prefix.


Overwrite Behavior

KV v1 replaces the entire secret on every write. There’s no versioning:

# Initial write
vault kv put kv-v1/myapp username="admin" password="old-pass" env="prod"

# Overwrite β€” the entire secret is replaced
vault kv put kv-v1/myapp password="new-pass"

# Read β€” username and env are GONE
vault kv get kv-v1/myapp

Expected output:

====== Data ======
Key         Value
---         -----
password    new-pass

⚠️ Warning: The previous username and env fields are permanently lost. KV v1 has no undo.


List Secrets

# Create multiple secrets
vault kv put kv-v1/app/dev db_host="dev-db.local"
vault kv put kv-v1/app/staging db_host="staging-db.local"
vault kv put kv-v1/app/prod db_host="prod-db.local"

# List secrets at a path
vault kv list kv-v1/app/

Expected output:

Keys
----
dev
prod
staging

Delete Secrets

Deleting in KV v1 is permanent β€” there’s no soft-delete or undelete:

# Delete a secret
vault kv delete kv-v1/app/dev

# Try to read it
vault kv get kv-v1/app/dev
# No value found at kv-v1/app/dev

When to Use KV v1

Use Case KV v1 KV v2
Simple secret storage βœ… βœ…
Need version history ❌ βœ…
Need soft-delete/undelete ❌ βœ…
Need custom metadata ❌ βœ…
Lower storage overhead βœ… ❌
Simpler API paths βœ… ❌

Rule of thumb: Use KV v2 unless you have a specific reason to use v1 (storage constraints, simpler API integration).


Quick Reference

Command Description
vault secrets enable -path=kv-v1 -version=1 kv Enable KV v1
vault kv put kv-v1/path key=value Write secret
vault kv get kv-v1/path Read secret
vault kv get -field=key kv-v1/path Read single field
vault kv list kv-v1/path/ List secrets
vault kv delete kv-v1/path Delete (permanent)

Next: KV Version 2