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)

Note
Though this tutorial focuses on the demonstration of the Vault Agent Auto-Auth using the kubernetes auth method for application integration, there are two other options. The other options are the Vault Secrets Operator and the Agent Sidecar Injector.
Refer to Vault Secrets Operator and Injecting Secrets into Kubernetes Pods via Vault Helm Sidecar for a step-by-step tutorial on these options.
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-agent- This 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
Working directory
Run the remainder of commands in the tutorial from this directory.
Start a Vault server
- To go through this tutorial, start a Vault dev server which listens for requests locally at - 0.0.0.0:8200with- rootas the root token ID.- $ vault server -dev -dev-root-token-id root -dev-listen-address 0.0.0.0:8200- Setting the - -dev-listen-addressto- 0.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.- Insecure operation - Do not run a Vault dev server in production. This approach simplifies the unsealing process for this tutorial. 
- 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 start
- Wait 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: Configured
- In 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: default
- Create the - vault-authservice account.- $ kubectl apply --filename vault-auth-service-account.yaml
- Create 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-token
- Create 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"] } EOF
- Create 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 1
- Set the environment variables to point to the running minikube environment. - Set the - SA_SECRET_NAMEenvironment variable value to the- vault-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/config- You can validate the issuer name of your Kubernetes cluster found under Discovering the service account issuer. 
- Create 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=24h- Output: - Success! Data written to: auth/kubernetes/role/example
Audience field and the service account JWT
When creating a named role, Vault 1.21+ recommends the audience field to match the aud field of the JWT service account token. This is a security measure to ensure that the token intended for use with Service Account is correct.
Security Considerations
The pattern Vault uses to authenticate Pods depends on sharing the JWT token over the network. Given the security model of Vault, this is allowable because Vault is part of the trusted compute base. In general, Kubernetes applications should not share this JWT with other applications. It allows API calls on behalf of the Pod and can result in unintended access from 3rd parties.
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 login
- Within this SSH session, retrieve the value of the minikube host. - $ dig +short host.docker.internal 192.168.65.2- Docker networking - The host has a changing IP address (or none if you have no network access). Connect to the special DNS name - host.docker.internalwhich resolves to the internal IP address used by the host. This is for development purpose and will not work in production. For more information, review the documentation for Mac, Windows.
- Next, 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. - $ exit
- Create 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" EOF- The Pod - devwebappruns with the- vault-authservice account.
- Create the - devwebapppod in the- defaultKubernetes namespace.- $ kubectl apply --filename devwebapp.yaml --namespace default pod/devwebapp created
- Display 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 77s
- Start 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 the- KUBE_TOKEN.- $ curl --request POST \ --data '{"jwt": "'"$KUBE_TOKEN"'", "role": "example"}' \ $VAULT_ADDR/v1/auth/kubernetes/login | python3 -m json.tool- Example 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_tokenwith- myapp-kv-ropolicy attached to the token. The- metadatadisplays that its service account name (- service_account_name) is- vault-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: default- This creates a Vault Agent configuration file, - vault-agent-config.hcl. The Vault Agent- auto_authblock uses the- kubernetesauth method enabled at the- auth/kubernetespath. The Vault Agent will use the- examplerole 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-Auth- sinkblocks if you want Vault Agent to place the token into multiple locations. In this example, set the- sinkto- /home/vault/.vault-token.- The - templateblock creates a file which retrieves- usernameand- passwordvalues at the- secret/data/myapp/configpath.
- Create a ConfigMap containing a Vault Agent configuration. - $ kubectl create --filename configmap.yaml configmap/example-vault-agent-config created
- View the created ConfigMap. - $ kubectl get configmap example-vault-agent-config --output yaml
- Review 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-data- The example Pod spec ( - example-k8s-spec.yaml) spins up two containers in- vault-agent-examplepod. A Vault container which runs Vault Agent as an Init Container and an- nginxcontainer exposing port 80. - The Vault address environment variable, - VAULT_ADDR, uses a placeholder value- EXTERNAL_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.yaml
- Create the - vault-agent-examplepod defined in- vault-agent-example.yaml.- $ kubectl apply --filename vault-agent-example.yaml- This takes a minute or so for the pod to become fully up and running. 
Verification
- In another terminal, launch the minikube dashboard. - $ minikube dashboard
- Under 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 port- 80on the- vault-agent-examplepod.- $ kubectl port-forward pod/vault-agent-example 8080:80
- In a web browser, go to - localhost:8080 - The page displays the values for - usernameand- passwordfrom- secret/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" cluster
- Delete the supporting code repository copied locally. - $ cd ../.. && rm -rf learn-vault-agent
- Stop 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