Vault
Retrieve secrets for Kubernetes workloads with Vault Agent
Most requests to Vault require an authentication token. This includes all API requests, Vault CLI commands, and other libraries. If you can securely get the first secret from an originator to a consumer, later secrets transmitted between the originator and consumer authenticate with the trust established by the successful distribution and user of the first secret.

Challenge
The applications running in a Kubernetes environment is no exception. Vault provides Kubernetes auth method to authenticate the clients using a Kubernetes Service Account Token.

However, the client is still responsible for managing the lifecycle of its Vault tokens. Therefore, the next challenge becomes how to manage the lifecycle of tokens in a standard way without having to write custom logic.
Solution
Vault Agent provides a number of different helper features, specifically addressing the following challenges:
- Automatic authentication
- Secure delivery/storage of tokens
- Lifecycle management of these tokens (renewal & re-authentication)

Prerequisites
To perform the tasks described in this tutorial, you need to have:
- Docker
- Kubernetes command-line interface (CLI)
- minikube
- A running Vault environment reachable from your Kubernetes environment. Refer to the Vault install guide to install Vault. Make sure that your Vault server has been initialized and unsealed
Install supporting tools
This tutorial was last tested 11 August 2025 on a macOS 15.5 using the following software versions.
$ docker version
Client:
Cloud integration: v1.0.35+desktop.5
Version: 27.5.1
...
$ kubectl version --short
Client Version: v1.33.2
Kustomize Version: v5.6.0
Server Version: v1.33.1
$ minikube version
minikube version: v1.36.0
commit: f8f52f5de11fc6ad8244afac475e1d0f96841df1
Retrieve the configuration by cloning the hashicorp-education/learn-vault-agent repository from GitHub.
$ git clone https://github.com/hashicorp-education/learn-vault-agentThis repository has supporting content for the Vault tutorials. The content specific to this tutorial is in a sub-directory.
Go into the
learn-vault-agent/vault-agent-k8s-demodirectory.$ cd learn-vault-agent/vault-agent-k8s-demo
Start a Vault server
To go through this tutorial, start a Vault dev server which listens for requests locally at
0.0.0.0:8200withrootas the root token ID.$ vault server -dev -dev-root-token-id root -dev-listen-address 0.0.0.0:8200Setting the
-dev-listen-addressto0.0.0.0:8200overrides the default address of a Vault dev server (127.0.0.1:8200) and enables Vault to be addressable by the Kubernetes cluster and its pods because it binds to a shared network.Export an environment variable for the
vaultCLI to connect to the Vault server.$ export VAULT_ADDR=http://0.0.0.0:8200
Create a service account
Start a Kubernetes cluster running in minikube.
$ minikube startWait a couple of minutes for the minikube environment to become fully available.
$ minikube status minikube type: Control Plane host: Running kubelet: Running apiserver: Running kubeconfig: ConfiguredIn Kubernetes, a service account provides an identity for processes that run in a Pod so that the processes can contact the API server. Open the provided
vault-auth-service-account.yamlfile in your preferred text editor and examine its content used for the service account definition in this tutorial.vault-auth-service-account.yaml
# Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 apiVersion: v1 kind: ServiceAccount metadata: name: vault-auth namespace: default --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: role-tokenreview-binding namespace: default roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: system:auth-delegator subjects: - kind: ServiceAccount name: vault-auth namespace: defaultCreate the
vault-authservice account.$ kubectl apply --filename vault-auth-service-account.yamlCreate the service account secret.
vault-auth-secret.yaml
# Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 apiVersion: v1 kind: Secret metadata: name: vault-auth-secret annotations: kubernetes.io/service-account.name: vault-auth type: kubernetes.io/service-account-tokenCreate a
vault-auth-secretsecret.$ kubectl apply --filename vault-auth-secret.yaml
Configure Kubernetes auth method
Create the
myapp-kv-roVault policy.$ vault policy write myapp-kv-ro - <<EOF path "secret/data/myapp/*" { capabilities = ["read", "list"] } EOFCreate some test data at the
secret/myapppath.$ vault kv put secret/myapp/config \ username='appuser' \ password='suP3rsec(et!' \ ttl='30s'Output:
====== Secret Path ====== secret/data/myapp/config ======= Metadata ======= Key Value --- ----- created_time 2022-03-24T06:09:49.99472Z custom_metadata <nil> deletion_time n/a destroyed false version 1Set the environment variables to point to the running minikube environment.
Set the
SA_SECRET_NAMEenvironment variable value to thevault-authservice account secret.$ export SA_SECRET_NAME=$(kubectl get secrets --output=json \ | jq -r '.items[].metadata | select(.name|startswith("vault-auth-")).name')Set the
SA_JWT_TOKENenvironment variable value to the service account JWT used to access the TokenReview API$ export SA_JWT_TOKEN=$(kubectl get secret $SA_SECRET_NAME \ --output 'go-template={{ .data.token }}' | base64 --decode)Set the
SA_CA_CRTenvironment variable value to the PEM encoded CA cert used to talk to Kubernetes API.$ export SA_CA_CRT=$(kubectl config view --raw --minify --flatten --output 'jsonpath={.clusters[].cluster.certificate-authority-data}' | base64 --decode)Set the
K8S_HOSTenvironment variable value to minikube IP address.$ export K8S_HOST=$(kubectl config view --raw --minify --flatten \ --output 'jsonpath={.clusters[].cluster.server}')Now, enable and configure the Kubernetes auth method.
Enable the Kubernetes auth method at the default path ("auth/kubernetes").
$ vault auth enable kubernetes Success! Enabled kubernetes auth method at: kubernetes/Tell Vault how to communicate with the Kubernetes (minikube) cluster.
$ vault write auth/kubernetes/config \ token_reviewer_jwt="$SA_JWT_TOKEN" \ kubernetes_host="$K8S_HOST" \ kubernetes_ca_cert="$SA_CA_CRT" \ issuer="https://kubernetes.default.svc.cluster.local"Output:
Success! Data written to: auth/kubernetes/configCreate a role named,
example, that maps the Kubernetes Service Account to Vault policies and default token TTL.$ vault write auth/kubernetes/role/example \ bound_service_account_names=vault-auth \ bound_service_account_namespaces=default \ token_policies=myapp-kv-ro \ audience=https://kubernetes.default.svc.cluster.local \ ttl=24hOutput:
Success! Data written to: auth/kubernetes/role/example
Determine the Vault address
A service bound to all networks on the host, as you configured Vault, is addressable by pods within minikube's cluster by sending requests to the gateway address of the Kubernetes cluster.
Start a minikube SSH session.
$ minikube ssh ## ... minikube ssh loginWithin this SSH session, retrieve the value of the minikube host.
$ dig +short host.docker.internal 192.168.65.2Next, retrieve the status of the Vault server to verify network connectivity.
$ dig +short host.docker.internal | xargs -I{} curl -s http://{}:8200/v1/sys/seal-status | python3 -m json.tool { "type": "shamir", "initialized": true, "sealed": false, "t": 1, "n": 1, "progress": 0, "nonce": "", "version": "1.4.1+ent", "migration": false, "cluster_name": "vault-cluster-3de6c2d3", "cluster_id": "10fd177e-d55a-d740-0c54-26268ed86e31", "recovery_seal": false, "storage_type": "inmem" }The output displays that the Vault server initialization status of
false. This confirms that pods within your cluster are able to reach Vault given that each pod uses the gateway address.Exit the minikube SSH session.
$ exitCreate a variable named
EXTERNAL_VAULT_ADDRto capture the minikube gateway address.$ EXTERNAL_VAULT_ADDR=$(minikube ssh "dig +short host.docker.internal" | tr -d '\r')Verify that the variable has the IP address you saw when executed in the minikube shell.
$ echo $EXTERNAL_VAULT_ADDR 192.168.65.2
Optional: Verify the Kubernetes auth method configuration
Define a Pod with a container.
$ cat > devwebapp.yaml <<EOF apiVersion: v1 kind: Pod metadata: name: devwebapp labels: app: devwebapp spec: serviceAccountName: vault-auth containers: - name: devwebapp image: burtlo/devwebapp-ruby:k8s env: - name: VAULT_ADDR value: "http://$EXTERNAL_VAULT_ADDR:8200" EOFThe Pod
devwebappruns with thevault-authservice account.Create the
devwebapppod in thedefaultKubernetes namespace.$ kubectl apply --filename devwebapp.yaml --namespace default pod/devwebapp createdDisplay all the pods in the default namespace. Wait until the
devwebapppod is running and ready (1/1).$ kubectl get pods NAME READY STATUS RESTARTS AGE devwebapp 1/1 Running 0 77sStart an interactive shell session on the
devwebapppod.$ kubectl exec --stdin=true --tty=true devwebapp -- /bin/sh #The command connects to the
devwebappcontainer.Set
KUBE_TOKENto the service account token.$ KUBE_TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)Authenticate with Vault through the
examplerole with theKUBE_TOKEN.$ curl --request POST \ --data '{"jwt": "'"$KUBE_TOKEN"'", "role": "example"}' \ $VAULT_ADDR/v1/auth/kubernetes/login | python3 -m json.toolExample output:
{ ...snip... "auth": { "client_token": "hvs.CAESIBxfvzds7M4tas017ls36Dl_kA-3YpCCBT9wczP1E41DGh4KHGh2cy5JYW1NMmFkb2gwTVBhZVhsN0pDM0tOaWM", "accessor": "1Miu5tbfuZzYuYWCntb4Ztke", "policies": [ "default", "myapp-kv-ro" ], "token_policies": [ "default", "myapp-kv-ro" ], "metadata": { "role": "example", "service_account_name": "vault-auth", "service_account_namespace": "default", "service_account_secret_name": "", "service_account_uid": "a293b5fb-96a2-43b0-acee-03d9ffce9423" }, "lease_duration": 86400, "renewable": true, "entity_id": "8925670b-d8d6-0792-ee56-eec1a9e740cb", "token_type": "service", "orphan": true, "mfa_requirement": null, "num_uses": 0 } }The request generates the
client_tokenwithmyapp-kv-ropolicy attached to the token. Themetadatadisplays that its service account name (service_account_name) isvault-auth.Exit the pod.
$ exit
Start Vault Agent with auto-auth
Now that you have verified the Kubernetes auth method configuration, start a client Pod which leverages Vault Agent to automatically authenticate with Vault and retrieve a client token.
First, open the provided
configmap.yamlfile in your preferred text editor and review its content.configmap.yaml
# Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 apiVersion: v1 data: vault-agent-config.hcl: | # Comment this out if running as sidecar instead of initContainer exit_after_auth = true pid_file = "/home/vault/pidfile" auto_auth { method "kubernetes" { mount_path = "auth/kubernetes" config = { role = "example" } } sink "file" { config = { path = "/home/vault/.vault-token" } } } template { destination = "/etc/secrets/index.html" contents = <<EOT <html> <body> <p>Some secrets:</p> {{- with secret "secret/data/myapp/config" }} <ul> <li><pre>username: {{ .Data.data.username }}</pre></li> <li><pre>password: {{ .Data.data.password }}</pre></li> </ul> {{ end }} </body> </html> EOT } kind: ConfigMap metadata: name: example-vault-agent-config namespace: defaultThis creates a Vault Agent configuration file,
vault-agent-config.hcl. The Vault Agentauto_authblock uses thekubernetesauth method enabled at theauth/kubernetespath. The Vault Agent will use theexamplerole which you created in Configure Kubernetes auth method.The
sinkblock specifies the location on disk where to write tokens. You can configure different Vault Agent Auto-Authsinkblocks if you want Vault Agent to place the token into multiple locations. In this example, set thesinkto/home/vault/.vault-token.The
templateblock creates a file which retrievesusernameandpasswordvalues at thesecret/data/myapp/configpath.Create a ConfigMap containing a Vault Agent configuration.
$ kubectl create --filename configmap.yaml configmap/example-vault-agent-config createdView the created ConfigMap.
$ kubectl get configmap example-vault-agent-config --output yamlReview the provided example Pod spec file,
example-k8s-spec.yaml.example-k8s-spec.yaml
apiVersion: v1 kind: Pod metadata: name: vault-agent-example namespace: default spec: serviceAccountName: vault-auth volumes: - configMap: items: - key: vault-agent-config.hcl path: vault-agent-config.hcl name: example-vault-agent-config name: config - emptyDir: {} name: shared-data initContainers: - args: - agent - -config=/etc/vault/vault-agent-config.hcl - -log-level=debug env: - name: VAULT_ADDR value: http://EXTERNAL_VAULT_ADDR:8200 image: hashicorp/vault name: vault-agent volumeMounts: - mountPath: /etc/vault name: config - mountPath: /etc/secrets name: shared-data containers: - image: nginx name: nginx-container ports: - containerPort: 80 volumeMounts: - mountPath: /usr/share/nginx/html name: shared-dataThe example Pod spec (
example-k8s-spec.yaml) spins up two containers invault-agent-examplepod. A Vault container which runs Vault Agent as an Init Container and annginxcontainer exposing port 80.
The Vault address environment variable,
VAULT_ADDR, uses a placeholder valueEXTERNAL_VAULT_ADDR.Generate the Pod spec with
EXTERNAL_VAULT_ADDRvariable value in its place.$ cat example-k8s-spec.yaml | \ sed -e s/"EXTERNAL_VAULT_ADDR"/"$EXTERNAL_VAULT_ADDR"/ \ > vault-agent-example.yamlCreate the
vault-agent-examplepod defined invault-agent-example.yaml.$ kubectl apply --filename vault-agent-example.yamlThis takes a minute or so for the pod to become fully up and running.
Verification
In another terminal, launch the minikube dashboard.
$ minikube dashboardUnder Workloads click Pods to verify that
vault-agent-examplepod has is running.
Select vault-agent-example to see its details.

In another terminal, port forward all requests made to
http://localhost:8080to port80on thevault-agent-examplepod.$ kubectl port-forward pod/vault-agent-example 8080:80In a web browser, go to
localhost:8080
The page displays the values for
usernameandpasswordfromsecret/myapp/config.View the HTML source.
$ kubectl exec -it vault-agent-example --container nginx-container -- cat /usr/share/nginx/html/index.html <html> <body> <p>Some secrets:</p> <ul> <li><pre>username: appuser</pre></li> <li><pre>password: suP3rsec(et!</pre></li> </ul> </body> </html>
Clean up
Stop the cluster.
$ minikube stop ✋ Stopping node "minikube" ... 🛑 Powering off "minikube" via SSH ... 🛑 1 node stopped.Delete the cluster.
$ minikube delete 🔥 Deleting "minikube" in docker ... 🔥 Deleting container "minikube" ... 🔥 Removing /Users/mrken/.minikube/machines/minikube ... 💀 Removed all traces of the "minikube" clusterDelete the supporting code repository copied locally.
$ cd ../.. && rm -rf learn-vault-agentStop the Vault server process.
$ pkill vault
Help and reference
- Blog post: Why Use the Vault Agent for Secrets Management?
- Video: Streamline Secrets Management with Vault Agent and Vault 0.11
- Secure Introduction of Vault Clients
- Vault Agent Auto-Auth
- Kubernetes Auth Method
- Kubernetes Auth Method (API)
- Vault Kubernetes Workshop on Google Cloud Platform
- Kubernetes Tutorials
- Consul Template
- Vault Secrets Operator
- Vault Secrets Operator Tutorial