Skip to content

Hashicorp Vault CLI Part 8: Secrets Management and Key-Value Engine

By Sebastian Günther

Hashicorp Vault is a tool for managing secrets and encrypted data. Upon successful authentication, a policy-based system authorizes access to Vault endpoints. All configuration aspects, as well as available functional featured, can be managed via its CLI.

In an ongoing series, all CLI commands are explored systematically. The focus of this article are commands from the secret’s management group. Specifically, the management if secret engines themselves, and commands for working with encrypted data.

The technical context of this article is hashicorp_vault_v1.21.1, released 2025-11-18. All provided information and command examples should be valid with newer versions too, baring update to the syntax of CLI commands.

The background material for this article stems from the official Hashicorp Vault documentation about Vault CLI and subsequent pages, as well as information from the binary itself.

Vault CLI Overview

The Vault CLI provides more than 30 commands. For systematically explaining and contextualizing each command, they can be structured as follows.

Groups marked with a checkmark were covered in an earlier article, and the commands marked with an at sign are the focus for this article.

  • ✅ Initialization
    • server: Starts a server process
    • agent: Starts an agent process, a utility to communicate with a vault server to gain access to tokens
    • proxy: Starts a vault proxy process
  • ✅ Configuration
    • operator: Cluster management operations, including memberships, encryption and unseal keys
    • plugin: Manage and install additional plugins
    • read / list: Access stored configuration and secrets
    • write / patch: Modify or create any data
    • delete: Delete configuration data or secrets
  • ✅ Introspection
    • status: Show status information of the vault server
    • version: Shows compact version information and build timestamp
    • version-history: Shows detailed version information about all previously used vault server instances
    • print: Detailed view of the vault’s server runtime configuration
    • path-help: Detailed documentation about API endpoints
    • events: Subscribe to the event stream of a running vault instance
    • monitor: Print vault log messages
    • debug: Shows debug information of the connected Vault server
    • audit: Interact with connected audit devices
  • ✅ Vault Enterprise
    • hcp: Operate a managed Hashicorp Vault cluster
    • namespace: Interact with configured namespaces of the cluster
  • ✅ Authorization
    • policy: Manage policy definitions that govern all vault operations
    • tokens: General token management
    • lease: Manage current token leases, including renewal, revocation and TTL modification
  • ✅ Authentication
    • auth: Interact with configured authentication options
    • login: Authenticates access to a Vault server
  • Secrets Management
    • 🌀 secrets: General configuration of secret engines
    • 🌀 kv: Access to the essential key-value store
    • transform: Interact with the transform secrets engine
    • transit: Interact with the Vaults transit secrets engine
    • unwrap: One-time access to arbitrary encrypted data
    • pki: Access the private key infrastructure secrets engine
    • ssh: Initiates SSH sessions via the SSH secrets engine

Secret Engine Management

Secrets engines manage different types of encrypted data. They can be grouped into native, applications and services, cloud provider, and encryption keys. For a detailed explanation, see my earlier article about Secret Management Engines

The secret command governs the complete life-cycle of an engine, from activation to re-configuration and dismantling. In the following sections, a kv-v2 engine will be regarded.

kv secrets enable

Every engine needs to be activated at first. A minimalist invocation of the enable command with just the secret engine name applies its default configuration including the mount path. Alternatively, all exposed configuration options can be passed as flags to customize the engine right from the start.

To activate a kv-v2 engine with default properties:

> vault secrets enable kv-v2

# Log messages
2025-12-19T09:01:22.459+0100 [INFO]  core: successful mount: namespace="" path=kv-v2/ type=kv version="v0.25.0+builtin"

Custom configurations can be applied during setup. The list of options is long, and support varies by engine. Available are:

  • default-lease-ttl=<duration>: The TTL value for all leases issued by the engine
  • description=<string>: Additional documentation for this secret engine, intended for users
  • external-entropy-access=<bool>: Allow this engine to access external entropy sources
  • force-no-cache=<bool>: Configure the caching behavior of the engine
  • listing-visibility=<string>: Controls if the engine should be visible to authenticated unauthenticated users. Allowed values are hidden and unauth.
  • local=<bool>: Secret engines configuration and values are normally replicated in the context of running Vault as a cluster. This option can disable this behavior.
  • max-lease-ttl=<duration>: The maximum TTL of issues leases. If this time passes, the lease can not be renewed again.
  • options=<key=value>: Additional generic options passed to the engine.
  • path=<string>: The mount path of the engine. Needs to be unique.
  • plugin-name=<string>: Vaults plugin nature allows developers to implement custom engine. This configuration option sets the correct plugin to use.
  • plugin-version=<string>: Set the plugins version to use.
  • seal-wrap: All secrets are encrypted with Vault-internal keys. This option allows to use additional keys for storing and reading secret data.
  • version=<int>: The version of the secret engine.

Here is an example to create the kv-v2 engine with specific options.

> vault secrets enable \
  -description="datacenter secrets" \
  -external-entropy-access=true \
  -listing-visibility=hidden \
  -force-no-cache=true -path=kv2 \
  -default-lease-ttl=5m \
  -max-lease-ttl=1h \
  kv-v2

# Log messages
2025-12-19T09:52:44.260+0100 [INFO]  core: successful mount: namespace="" path=kv2/ type=kv version="v0.25.0+builtin"

kv secrets list

This command shows all currently activated secret engines. The output can be formatted as table, JSON or YAML, and the -detailed flags

> vault secrets list -format=json

# Log messages
{
  "cubbyhole/": {
    "uuid": "2da6947a-8eb4-8bca-7405-8d056e87d997",
    "type": "cubbyhole",
    "description": "per-token private secret storage",
    "accessor": "cubbyhole_376f2732",
    "config": {
      "default_lease_ttl": 0,
      "max_lease_ttl": 0,
      "force_no_cache": false
    },
    "options": null,
    "local": true,
    "seal_wrap": false,
    "external_entropy_access": false,
    "plugin_version": "",
    "running_plugin_version": "v1.21.1+builtin.vault",
    "running_sha256": "",
    "deprecation_status": ""
  },
  "identity/": {
    "uuid": "985d0186-a541-5905-fabb-70352eaf55b9",
    "type": "identity",
    "description": "identity store",
    "accessor": "identity_6676eb9f",
    "config": {
      "default_lease_ttl": 0,
      "max_lease_ttl": 0,
      "force_no_cache": false,
      "passthrough_request_headers": [
        "Authorization"
      ]
    },
    "options": null,
    "local": false,
    "seal_wrap": false,
    "external_entropy_access": false,
    "plugin_version": "",
    "running_plugin_version": "v1.21.1+builtin.vault",
    "running_sha256": "",
    "deprecation_status": ""
  },
  "kv2/": {
    "uuid": "f7192663-902b-bc9f-da68-762c82c3738b",
    "type": "kv",
    "description": "datacenter secrets",
    "accessor": "kv_20ee0106",
    "config": {
      "default_lease_ttl": 300,
      "max_lease_ttl": 3600,
      "force_no_cache": true,
      "listing_visibility": "hidden"
    },
    "options": {
      "version": "2"
    },
    "local": false,
    "seal_wrap": false,
    "external_entropy_access": true,
    "plugin_version": "",
    "running_plugin_version": "v0.25.0+builtin",
    "running_sha256": "",
    "deprecation_status": "supported"
  },
  "postgres/": {
    "uuid": "50dae925-f07d-184c-a824-096e5719e213",
    "type": "database",
    "description": "",
    "accessor": "database_53cdbc55",
    "config": {
      "default_lease_ttl": 0,
      "max_lease_ttl": 0,
      "force_no_cache": false
    },
    "options": null,
    "local": false,
    "seal_wrap": false,
    "external_entropy_access": false,
    "plugin_version": "",
    "running_plugin_version": "v1.21.1+builtin.vault",
    "running_sha256": "",
    "deprecation_status": ""
  },
  "sys/": {
    "uuid": "9fc5f342-99e3-4d0a-b7c7-f3f015417a4e",
    "type": "system",
    "description": "system endpoints used for control, policy and debugging",
    "accessor": "system_5265b4ea",
    "config": {
      "default_lease_ttl": 0,
      "max_lease_ttl": 0,
      "force_no_cache": false,
      "passthrough_request_headers": [
        "Accept"
      ]
    },
    "options": null,
    "local": false,
    "seal_wrap": true,
    "external_entropy_access": false,
    "plugin_version": "",
    "running_plugin_version": "v1.21.1+builtin.vault",
    "running_sha256": "",
    "deprecation_status": ""
  },
}

kv secrets move

When a secrets engine mount path should be changed, this command can be used.

Here is an example to move the kv-v2 engine to a more descriptive mount path reflecting its intended use-case:

> vault secrets move kv2 datacenter-secrets/

# Log messages
2025-12-19T10:06:07.937+0100 [INFO]  core.mounts.migration: Starting to update the mount table and revoke leases: from_path=kv2/ migration_id=d5c3b5d9-d0b8-4bd8-4b9a-c29e471fb0e1 namespace="" to_path=datacenter-secrets/
2025-12-19T10:06:08.162+0100 [INFO]  core.mounts.migration: Removing the source mount from filtered paths on secondaries: from_path=kv2/ migration_id=d5c3b5d9-d0b8-4bd8-4b9a-c29e471fb0e1 namespace="" to_path=datacenter-secrets/
2025-12-19T10:06:08.162+0100 [INFO]  core.mounts.migration: Updating quotas associated with the source mount: from_path=kv2/ migration_id=d5c3b5d9-d0b8-4bd8-4b9a-c29e471fb0e1 namespace="" to_path=datacenter-secrets/
2025-12-19T10:06:08.162+0100 [INFO]  core.mounts.migration: Completed mount move operations: from_path=kv2/ migration_id=d5c3b5d9-d0b8-4bd8-4b9a-c29e471fb0e1 namespace="" to_path=datacenter-secrets/

kv secrets tune

Once a secret engine became operational, its configuration might need a modification. The tune command accepts most command from its activations, except those that govern the encryption itself, such as external-entropy-access

To modify the kv-v2 engine:

> vault secrets tune \
  -description="stores datacenter secrets" \
  -listing-visibility=unauth \
  -default-lease-ttl=10m datacenter-secrets

# Log messages
2025-12-19T10:23:13.086+0100 [INFO]  core: mount tuning of listing_visibility successful: path=datacenter-secrets/
2025-12-19T10:23:43.231+0100 [INFO]  core: mount tuning of leases successful: path=datacenter-secrets/
2025-12-19T10:23:43.351+0100 [INFO]  core: mount tuning of description successful: path=datacenter-secrets/ description="stores datacenter secrets"

kv secrets disable

When the secret engine is not required anymore, it can be turned off. All existing leases will be deleted, and all stored date is removed irreversible.

> vault secrets disable datacenter-secrets

# Log messages
2025-12-19T10:29:32.553+0100 [INFO]  core: successfully unmounted: path=datacenter-secrets/ namespace=""

Key-Value Secret Engine Data Commands

Vault comes with several built-in secrets engines, and the key-value store is the most generic one. Once configured, secrets in the form of key-value pairs can be stored at any nested path, allowing to reflect organizational or logical structure.

The key-value store is available in two different versions, a quick differentiation is this:

  • The kv-v1 store is more runtime efficient and requires fewer storage space. Secrets are stored unversioned. When a command overwrites data at an existing path, its data is lost. Furthermore, deletion is also non-recoverable.
  • The kv-v2 store adds versioning to all paths, with a default but tunable value of 10 versions. Storing data at an already defined path increments the version counter. Older versions can be read until the increments surpass the defined maximum value. Any version can be erased recoverable with the delete command, or non-recoverable with the destroy command. Finally, when using the vault CLI generic CRUD commands, the actual paths to access the secret need to differentiate between <mount_path>/data/<name> and <mount_path>/metadata/<name>.

Assuming a CRUD lifecycle, the kv subcommands can be seperated as follows:

  • Creation
    • put: Adds a new secret or new version of the secret at a defined path
  • Reading
    • list: Shows all secrets at a designated path, or at subsequent path parts
    • get: Access a specific secret and expose all its metadata
  • Update
    • patch: Modify existing data without incrementing the version
    • rollback: Restores a previous version of a secret
    • undelete: Restores a secret, or versions of a secrets, that were marked for deletion
    • enable-versioning: Adds versioning capabilities to a secret if not present already
  • Delete
    • delete: Removes a secret, or versions of a secret, but keeping an internal, recoverable record
    • destroy: Non-recoverable erasing of a secret or versions of secrets

Only in an kv-v2 store are all CLI commands available, and will therfore be the context for this section. The store is created with the following command:

> vault secrets enable -path=kv2 kv-v2

kv put

In its simplest form, a single key-value pair can be stored at an arbitrary path. The command can include the secret data directly, which will be stored in the Shell history and therefore exposed, or read from file.

To pass secrets directly:

> vault kv put kv2/api-creds aws=75ae33a4b907bc87796

# Log messages
=== Secret Path ===
kv2/data/api-creds

======= Metadata =======
Key                Value
---                -----
created_time       2025-12-20T08:13:57.536865Z
custom_metadata    <nil>
deletion_time      n/a
destroyed          false
version            1

Another option is to store the secret data in a file, and pass multiple key-value pairs to the command, where the key is determined by the command, and its value by the content of the file.

> vault kv put kv2/api-creds value=@/tmp/aws.scr.txt

# Log messages
=== Secret Path ===
kv2/data/api-creds

======= Metadata =======
Key                Value
---                -----
created_time       2025-12-20T08:33:49.738235Z
custom_metadata    <nil>
deletion_time      n/a
destroyed          false
version            2

Interestingly, the file content can even be binary, as the following example shows:

> dd if=/dev/urandom of=/tmp/binary.scr.txt bs=1 count=30

> vault kv put kv2/api-creds binary=@/tmp/binary.scr.txt

# Log messages
=== Secret Path ===
kv2/data/api-creds

======= Metadata =======
Key                binary
---                -----
created_time       2025-12-20T08:42:50.150485Z
custom_metadata    <nil>
deletion_time      n/a
destroyed          false
version            4

kv list

To get an overview about all secrets, the list command can be called with to secrets engines root-path.

> vault kv list kv2

# Log messages
Keys
----
api-creds
databases
kubernetes
kubernetes/

As the output shows, it lists the top-level paths only. When structured paths are defined, they will be shown with a trailing /. These paths need to be exposed additionally.

> vault kv list kv2/kubernetes/

# Log messages
Keys
----
datacenter1
datacenter2
datacenter3

kv get

This command retrieves all data stored at a specific path. Unless scoped, the most recent version will be returned.

Here is an example that returns the most-recent version, also showing that binary data was stored:

>  vault kv get kv2/api-creds

# Log messages
=== Secret Path ===
kv2/data/api-creds

======= Metadata =======
Key                Value
---                -----
created_time       2025-12-20T09:27:46.102492Z
custom_metadata    <nil>
deletion_time      n/a
destroyed          false
version            3

==== Data ====
Key      Value
---      -----
binary    7J\Z��!k�MT�t�����c�y�M/T

To return a different version instead:

> vault kv get -version=2 kv2/api-creds

# Log messages
=== Secret Path ===
kv2/data/api-creds

======= Metadata =======
Key                Value
---                -----
created_time       2025-12-20T09:26:19.01058Z
custom_metadata    <nil>
deletion_time      n/a
destroyed          false
version            2

==== Data ====
Key      Value
---      -----
value    aws=75ae33a4b907bc87796

kv patch

To amend additional data to an existing data record, two variants can be used. The put command requires the complete original data and the new, additional data to be specified. The patch command instead only requires to pass the additional data. In both cases, the version will be incremented.

Here is an example that adds a comment to the stored binary data:

> vault kv patch kv2/api-creds comment="Binary executable for retrieving secrets"

The new version has to following structure:

> vault kv get kv2/api-creds

# Log messages
=== Secret Path ===
kv2/data/api-creds

======= Metadata =======
Key                Value
---                -----
created_time       2025-12-20T10:09:59.494854Z
custom_metadata    <nil>
deletion_time      n/a
destroyed          false
version            4

===== Data =====
Key        Value
---        -----
comment    Binary executable for retrieving secrets
binary     7J\Z��!k�MT�t�����c�y�M/T

kv undelete

When a kv delete command for a specific version was issued, attempts to read the data will only return the metadata section with the additional attribute deletion_time.

Here is an example:

> vault kv delete -versions=4 kv2/api-creds
> vault kv get kv2/api-creds

# Log messages
=== Secret Path ===
kv2/data/api-creds

======= Metadata =======
Key                Value
---                -----
created_time       2025-12-20T10:09:59.494854Z
custom_metadata    <nil>
deletion_time      2025-12-21T06:42:28.086713Z
destroyed          false
version            4

The data can be restored, and read attempts succeed once again.

> vault kv undelete -versions=4 kv2/api-creds

# Log messages
Success! Data written to: kv2/undelete/api-creds

> vault kv get kv2/api-creds

# Log messages
=== Secret Path ===
kv2/data/api-creds

======= Metadata =======
Key                Value
---                -----
created_time       2025-12-20T10:09:59.494854Z
custom_metadata    <nil>
deletion_time      n/a
destroyed          false
version            4

===== Data =====
Key        Value
---        -----
comment    Binary executable for retrieving secrets
value      7J\Z��!k�MT�t�����c�y�M/T

kv rollback

The rollback command accesses a previous version of a secret, and stores the secrets value at a new, incremented version.

Here is an example of a rollback to version 2:

> vault kv rollback -versions=2 kv2/api-creds

# Log messages
Key                Value
---                -----
created_time       2025-12-21T06:49:06.178864Z
custom_metadata    <nil>
deletion_time      n/a
destroyed          false
version            5

> vault kv get kv2/api-creds

# Log messages
=== Secret Path ===
kv2/data/api-creds

======= Metadata =======
Key                Value
---                -----
created_time       2025-12-21T06:49:06.178864Z
custom_metadata    <nil>
deletion_time      n/a
destroyed          false
version            5

==== Data ====
Key      Value
---      -----
value    aws=75ae33a4b907bc87796

It is not possible to perform a rollback for a previously deleted version.

> vault kv delete -versions=4 kv2/api-creds

# Log messages
Success! Data deleted (if it existed) at: kv2/data/api-creds

# Log messages
> vault kv rollback -version=4 kv2/api-creds
Cannot roll back to a version that has been deleted

kv enable-versioning

As explained in the introductory paragraph, a kv-v1 store does not support versioned secrets. This command effectively turns the v1 to a v2 store.

Here is an example in which a kv-v1 store is created. First, the store is enabled at path kv, and two secrets stored.

> vault secrets enable -path=kv kv-v1

# Log messages
2025-12-21T08:41:57.535+0100 [INFO]  core: successful mount: namespace="" path=kv/ type=kv version="v0.25.0+builtin"
Success! Enabled the kv-v1 secrets engine at: kv/

> vault kv put kv/aws api_key=@/tmp/aws.scr.txt

# Log messages
Success! Data written to: kv/aws

> vault kv put kv/encryption_binary data==@/tmp/bin

# Log messages
Success! Data written to: kv/encryption_binary

> vault kv list kv

# Log messages
Keys
----
aws
encryption_binary

> vault kv get kv/aws

# Log messages
===== Data =====
Key        Value
---        -----
api_key    75ae33a4b907bc87796

Second, kv enable-versioning is applied, and a stored secret read. As shown, it now includes a metadata section, the sure sign that it is a kv-v2 secret.

> vault kv enable-versioning kv

# Log messages
2025-12-21T08:44:53.048+0100 [INFO]  core: mount tuning of options: path=kv/ options=map[version:2]
Success! Tuned the secrets engine at: kv/

2025-12-21T08:44:53.244+0100 [INFO]  secrets.kv.kv_845130a3: collecting keys to upgrade
2025-12-21T08:44:53.244+0100 [INFO]  secrets.kv.kv_845130a3: done collecting keys: num_keys=3
2025-12-21T08:44:54.174+0100 [INFO]  secrets.kv.kv_845130a3: upgrading keys finished

> vault kv list kv

# Log messages
Keys
----
aws
encryption_binary

> vault kv get kv/aws

# Log messages
== Secret Path ==
kv/data/aws

======= Metadata =======
Key                Value
---                -----
created_time       2025-12-21T07:44:53.342531Z
custom_metadata    <nil>
deletion_time      n/a
destroyed          false
version            1

===== Data =====
Key        Value
---        -----
api_key    75ae33a4b907bc87796

kv delete

In an kv-v2 store, the delete command modifies the data-record of a versioned secret. From that moment on, its metadata obtains a timestamp in the deletion_time attribute, and performing a kv get does not show the stored data anymore. This commands results are reversible - issuing a kv undelete restores the data, as shown above.

Here is an example:

> vault kv delete -versions=4 kv2/api-creds

# Log messages
Success! Data deleted (if it existed) at: kv2/data/api-creds

> vault kv get -version=4 kv2/api-creds

# Log messages
=== Secret Path ===
kv2/data/api-creds

======= Metadata =======
Key                Value
---                -----
created_time       2025-12-20T10:09:59.494854Z
custom_metadata    <nil>
deletion_time      2025-12-21T06:52:17.001191Z
destroyed          false
version            4

kv destroy

Similar to kv delete, this command modifies the data record. Its metadata shows destroyed true, and read attempts do not return the data anymore. This change is non-reversible - the data is removed permanently.

> vault kv destroy -versions=5 kv2/api-creds

# Log messages
Success! Data written to: kv2/destroy/api-creds
midi :: work/development/vault » vault kv get kv2/api-creds
=== Secret Path ===
kv2/data/api-creds

======= Metadata =======
Key                Value
---                -----
created_time       2025-12-21T06:49:06.178864Z
custom_metadata    <nil>
deletion_time      n/a
destroyed          true
version            5

Key-Value Secret Engine Metadata Commands

Each kv-v2 secret contains a data and a metadata section. The metadata section of a secret stored in a kv-v2 engine can be accessed and manipulated with the three subcommands get, put, and delete.

kv metadata get

Issuing a get returns the complete metadata history of a secret.

> vault kv metadata get kv2/api-creds

# Log messages
==== Metadata Path ====
kv2/metadata/api-creds

========== Metadata ==========
Key                     Value
---                     -----
cas_required            false
created_time            2025-12-20T09:26:11.531797Z
current_version         5
custom_metadata         <nil>
delete_version_after    0s
last_updated_by         map[actor:root client_id:0DHqvq2D77kL2/JTPSZkTMJbkFVmUu0TzMi0jiXcFy8= operation:update]
max_versions            0
oldest_version          0
updated_time            2025-12-21T06:49:06.178864Z

====== Version 1 ======
Key              Value
---              -----
created_by       map[actor:root client_id:0DHqvq2D77kL2/JTPSZkTMJbkFVmUu0TzMi0jiXcFy8= operation:create]
created_time     2025-12-20T09:26:11.531797Z
deleted_by       <nil>
deletion_time    n/a
destroyed        false

====== Version 2 ======
Key              Value
---              -----
created_by       map[actor:root client_id:0DHqvq2D77kL2/JTPSZkTMJbkFVmUu0TzMi0jiXcFy8= operation:update]
created_time     2025-12-20T09:26:19.01058Z
deleted_by       <nil>
deletion_time    n/a
destroyed        false

====== Version 3 ======
Key              Value
---              -----
created_by       map[actor:root client_id:0DHqvq2D77kL2/JTPSZkTMJbkFVmUu0TzMi0jiXcFy8= operation:update]
created_time     2025-12-20T09:27:46.102492Z
deleted_by       <nil>
deletion_time    n/a
destroyed        false

====== Version 4 ======
Key              Value
---              -----
created_by       map[actor:root client_id:0DHqvq2D77kL2/JTPSZkTMJbkFVmUu0TzMi0jiXcFy8= operation:patch]
created_time     2025-12-20T10:09:59.494854Z
deleted_by       map[actor:root client_id:0DHqvq2D77kL2/JTPSZkTMJbkFVmUu0TzMi0jiXcFy8= operation:update]
deletion_time    2025-12-21T06:52:17.001191Z
destroyed        false

====== Version 5 ======
Key              Value
---              -----
created_by       map[actor:root client_id:0DHqvq2D77kL2/JTPSZkTMJbkFVmUu0TzMi0jiXcFy8= operation:update]
created_time     2025-12-21T06:49:06.178864Z
deleted_by       <nil>
deletion_time    n/a
destroyed        true

kv metadata put

Each secrets' metadata property inherits their configuration from the store itself. These properties can be changed on a per-record base, customizing especially sensitive secrets.

Following properties are configurable:

  • cas-required=<*bool>: CAS is an acronym for "check-and-set". When this property is enabled, all data-record updates need to include the cas attribute present, and its value needs to be that of the most recent version number. Therefore, this setting is an additional fail-safe to prevent accidental modifications of the secret
  • delete-version-after=<duration>: Secrets can be configured as self-destructing with this setting. When the duration passes, the most recent version of the secret will be deleted. If version increments happen before the duration expires, older records remain readable.
  • max-versions=<int>: Per default, each secret path can be updated for 10 iterations before older data is purged. This flag modifies this property.
  • custom-metadata=<key=value>: The metadata section of each secret can include arbitrary, custom fields to augment information for system or human user access.

Here is an example of extending the metadata record with custom fields:

> vault kv metadata put -custom-metadata="comment=API credentials for AWS" -custom-metadata="public-key=ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHwxMpyeMvv1THOjNfBubFwtifqWO6nSZj2AS6n0fFoT" kv2/api-creds

# Log messages
Success! Data written to: kv2/metadata/api-creds

> vault kv get -format=JSON kv2/api-creds

# Log messages
{
  "request_id": "18da20a1-b67b-f781-ae10-a699675f3b80",
  "lease_id": "",
  "lease_duration": 0,
  "renewable": false,
  "data": {
    "data": {
      "api_key": "75ae33a4b907bc87796\n"
    },
    "metadata": {
      "created_time": "2025-12-21T09:36:44.286357Z",
      "custom_metadata": {
        "comment": "API credentials for AWS",
        "public-key": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHwxMpyeMvv1THOjNfBubFwtifqWO6nSZj2AS6n0fFoT"
      },
      "deletion_time": "",
      "destroyed": false,
      "version": 4
    }
  },
  "warnings": null,
  "mount_type": "kv"
}

The following experiment shows how the duration setting auto-deletes secrets. First, the duration is set to 1min. Second, a new version is created, and the return value shows a deletion timestamp in the future.

> vault kv metadata put -delete-version-after=1m kv2/api-creds

# Log messages
Success! Data written to: kv2/metadata/api-creds

> vault kv put kv2/api-creds api_key=@/tmp/aws.scr.txt

# Log messages
=== Secret Path ===
kv2/data/api-creds

======= Metadata =======
Key                Value
---                -----
created_time       2025-12-21T09:43:58.324523Z
custom_metadata    map[comment:API credentials for AWS public-key:ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHwxMpyeMvv1THOjNfBubFwtifqWO6nSZj2AS6n0fFoT]
deletion_time      2025-12-21T09:44:58.324523Z
destroyed          false
version            6

Once the time passed, the record is deleted. Executing a undelete command makes the secret available again.

vault kv get kv2/api-creds

# Log messages
=== Secret Path ===
kv2/data/api-creds

======= Metadata =======
Key                Value
---                -----
created_time       2025-12-21T09:43:58.324523Z
custom_metadata    map[comment:API credentials for AWS public-key:ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHwxMpyeMvv1THOjNfBubFwtifqWO6nSZj2AS6n0fFoT]
deletion_time      2025-12-21T09:44:58.324523Z
destroyed          false
version            6

> vault kv undelete -versions=7 kv2/api-creds

# Log messages
Success! Data written to: kv2/undelete/api-creds

> vault kv get kv2/api-creds

# Log messages
=== Secret Path ===
kv2/data/api-creds

======= Metadata =======
Key                Value
---                -----
created_time       2025-12-21T09:43:58.324523Z
custom_metadata    map[comment:API credentials for AWS public-key:ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHwxMpyeMvv1THOjNfBubFwtifqWO6nSZj2AS6n0fFoT]
deletion_time      2025-12-21T09:48:18.574148Z
destroyed          false
version            6

===== Data =====
Key        Value
---        -----
api_key    aws=75ae33a4b907bc87796

kv metadata delete

This command results in the immediate, non-recoverable destruction of all versions of a secret's data and metadata.

> vault kv metadata delete kv2/api-creds

# Log messages
Success! Data deleted (if it existed) at: kv2/metadata/api-creds

All following read attempts result in an error.

> vault kv metadata get kv2/api-creds

# Log messages
No value found at kv2/metadata/api-creds

Conclusion

The Hashicorp Vault CLI is a powerful tool for setup, configuration and maintenance of a Vault server or cluster. In an ongoing article series, all CLI commands are systematically explored. The focus for this article is two commands from the secret management group. With secrets, the available engines can be activated, their configuration read and modified, and finally disabled. Similarly, for the built-in key-value store engine, the kv subcommands cover all lifecycles. Here, you learned about the differences of kv-v1 and kv-v2 stores, and saw the intricacies of secret lifecycles with recoverable and non-recoverable deletion. Finally, you also saw how a secrets metadata can be accessed and manipulated, setting e.g. a property that automatically deletes secrets after a defined duration passed. Overall, this coverage should help you to use the key-value store with its full functionality.