Vault
Integrate Kubernetes with an external Vault cluster
Vault can manage secrets for Kubernetes application pods from outside the cluster. This could be HashiCorp Cloud Platform (HCP) Vault or another Vault service within your organization.

In this tutorial, you will run Vault locally, and start a Kubernetes cluster with minikube. You will deploy an application that retrieves secrets directly from Vault via a Kubernetes service and secret injection via Vault Agent Injector.
Prerequisites
- Vault version 1.13.0 or later
- Docker
- Kubernetes CLI
- minikube
- Helm CLI
- Vault CLI
This tutorial was last tested 2 February 2025 on a macOS 26.0.1 using this configuration.
$ docker version
Client:
Version: 29.0.1
## ...
$ kubectl version --client
Also try: k version --client
Client Version: v1.35.0
Kustomize Version: v5.7.1
$ minikube version
minikube version: v1.37.0
commit: 65318f4cfff9c12cc87ec9eb8f4cdd57b25047f3
$ helm version
version.BuildInfo{Version:"v4.0.5", GitCommit:"1b6053d48b51673c5581973f5ae7e104f627fcf5", GitTreeState:"clean", GoVersion:"go1.25.5", KubeClientVersion:"v1.34"}
$ vault version
Vault v1.21.1 (2453aac2638a6ae243341b4e0657fd8aea1cbf18), built 2025-11-18T13:04:32Z
These are the recommended software versions and the output displayed may vary depending on your environment and the software versions you use.
The GitHub repository
Next, retrieve the web application and additional configuration by cloning the hashicorp-education/learn-vault-external-kubernetes repository from GitHub.
$ git clone https://github.com/hashicorp-education/learn-vault-external-kubernetesGo into the
learn-vault-external-kubernetesdirectory.$ cd learn-vault-external-kubernetes
Lab setup
Open a terminal and start a Vault dev server with
rootas the root token.$ vault server -dev -dev-root-token-id rootThe Vault dev server defaults to running at
127.0.0.1:8200. The server is initialized and unsealed.Export an environment variable for the
vaultCLI to address the Vault server.$ export VAULT_ADDR=http://127.0.0.1:8200Export an environment variable for the
vaultCLI to authenticate with the Vault server.$ export VAULT_TOKEN=rootThe Vault server is ready.
Create a secret at path
secret/devwebapp/configwith ausernameandpassword.$ vault kv put secret/devwebapp/config username='giraffe' password='salsa' ======== Secret Path ======== secret/data/devwebapp/config ======= Metadata ======= Key Value --- ----- created_time 2022-06-06T18:26:14.070155Z custom_metadata <nil> deletion_time n/a destroyed false version 1Verify the ability to get the secret at the path
secret/devwebapp/config.$ vault kv get secret/devwebapp/config ======== Secret Path ======== secret/data/devwebapp/config ======= Metadata ======= Key Value --- ----- created_time 2026-01-22T17:23:28.942367Z custom_metadata <nil> deletion_time n/a destroyed false version 1 ====== Data ====== Key Value --- ----- password salsa username giraffe
Start minikube
minikube is a CLI tool that provisions and manages the lifecycle of single-node Kubernetes clusters locally inside Virtual Machines (VM) on your system.
Start a Kubernetes cluster.
$ minikube start 😄 minikube v1.25.2 on Darwin 12.3 ✨ Automatically selected the docker driver. Other choices: hyperkit, virtualbox, ssh 👍 Starting control plane node minikube in cluster minikube 🚜 Pulling base image ... 🔥 Creating docker container (CPUs=2, Memory=8100MB) ... 🐳 Preparing Kubernetes v1.23.3 on Docker 20.10.12 ... ▪ kubelet.housekeeping-interval=5m ▪ Generating certificates and keys ... ▪ Booting up control plane ... ▪ Configuring RBAC rules ... 🔎 Verifying Kubernetes components... ▪ Using image gcr.io/k8s-minikube/storage-provisioner:v5 🌟 Enabled addons: storage-provisioner 🏄 Done! kubectl is now configured to use "minikube" cluster and "default" namespace by defaultThe initialization process takes several minutes as it retrieves any necessary dependencies and executes various container images.
Verify the status of the minikube cluster.
$ minikube status minikube type: Control Plane host: Running kubelet: Running apiserver: Running kubeconfig: ConfiguredThe host, kubelet, apiserver report that they are running. The
kubectl, a command line interface (CLI) for running commands against Kubernetes cluster, is also configured to communicate with this recently started cluster.
Determine the Vault address
A service bound to all networks on the host, as you configured Vault, is addressable by pods in 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.2Retrieve 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.5.0", "migration": false, "cluster_name": "vault-cluster-44ba824c", "cluster_id": "adc0bb6a-e330-3e7a-e0c7-38061c3bf191", "recovery_seal": false, "storage_type": "inmem" }
The output shows that Vault is initialized and unsealed. This confirms that pods in the cluster are able to reach Vault. This output is identical to the output from running vault status.
Exit the minikube SSH session.
$ exitCreate a variable named EXTERNAL_VAULT_ADDR to capture the minikube. gateway address.
$ EXTERNAL_VAULT_ADDR=$(minikube ssh "dig +short host.docker.internal" | tr -d '\r')Verify that the variable has an IP address.
$ echo $EXTERNAL_VAULT_ADDR 192.168.65.2
Deploy application with hardcoded Vault address
The most direct way for a pod in the cluster to address Vault is with a hardcoded network address defined in the application code or provided as an environment variable. This web application that allows override of the Vault address.
Create a Kubernetes service account named
internal-app.$ kubectl create sa internal-appDefine a pod named
devwebappwith the web application that sets theVAULT_ADDRtoEXTERNAL_VAULT_ADDR.$ cat > devwebapp.yaml <<EOF apiVersion: v1 kind: Pod metadata: name: devwebapp labels: app: devwebapp spec: serviceAccountName: internal-app containers: - name: app image: burtlo/devwebapp-ruby:k8s env: - name: VAULT_ADDR value: "http://$EXTERNAL_VAULT_ADDR:8200" - name: VAULT_TOKEN value: root EOFCreate the
devwebapppod.$ kubectl apply --filename devwebapp.yaml pod/devwebapp createdGet all the pods in the default namespace.
$ kubectl get pods NAME READY STATUS RESTARTS AGE devwebapp 1/1 Running 0 4mWait until the
devwebapppod reports that is running and ready (1/1).Request content served at
localhost:8080from within thedevwebapppod.$ kubectl exec devwebapp -- curl -s localhost:8080 ; echoThe result displays the secret at the path
secret/data/devwebapp/config.{"password"=>"salsa", "username"=>"giraffe"}
The web application authenticates with the external Vault server using the root
token and returns the secret defined at the path secret/data/devwebapp/config.
This hardcoded approach is an effective solution if the address to the Vault
server does not change.
Deploy service and endpoints to address an external Vault
An external Vault may not have a static network address that services in the cluster can rely upon. When Vault's network address changes each service also needs to change to continue its operation. Another approach to manage this network address is to define a Kubernetes service and endpoints.
A service creates an abstraction around pods or an external service. When an application running in a pod requests the service, it routs that request to the endpoints that share the service name.
Define a service named
external-vaultand a corresponding endpoint configured to address theEXTERNAL_VAULT_ADDR.$ cat > external-vault.yaml <<EOF --- apiVersion: v1 kind: Service metadata: name: external-vault namespace: default labels: run: external-vault spec: ports: - name: http port: 8200 targetPort: http selector: run: external-vault --- apiVersion: discovery.k8s.io/v1 kind: EndpointSlice metadata: name: external-vault labels: kubernetes.io/service-name: external-vault addressType: IPv4 endpoints: - addresses: - "$EXTERNAL_VAULT_ADDR" ports: - name: http port: 8200 --- EOFCreate the
external-vaultservice.$ kubectl apply --filename external-vault.yaml service/external-vault created endpoints/external-vault createdVerify that the
external-vaultservice is addressable from within thedevwebapppod.$ kubectl exec devwebapp -- curl -s http://external-vault:8200/v1/sys/seal-status | jqThe result displays the status of the Vault server.
Example output:
{ "type": "shamir", "initialized": true, "sealed": false, "t": 1, "n": 1, "progress": 0, "nonce": "", "version": "1.10.3", "migration": false, "cluster_name": "vault-cluster-92405644", "cluster_id": "e95a3cae-1321-e8d3-28f7-b6592ad23dee", "recovery_seal": false, "storage_type": "inmem" }Optionally, run a
vault statusand compare the results.Create a pod that sets the
VAULT_ADDRto theexternal-vaultservice.$ kubectl apply --filename=pod-devwebapp-through-service.yaml deployment.apps/devwebapp-through-service createdThe
devwebapp-through-servicepod addresses Vault through the service instead of the hardcoded network address.Get all the pods in the default namespace.
$ kubectl get pods NAME READY STATUS RESTARTS AGE devwebapp 1/1 Running 0 36m devwebapp-through-service 1/1 Running 0 20sWait until the
devwebapp-through-servicepod is running and ready (1/1).Request content served at
localhost:8080from within thedevwebapp-through-servicepod.$ kubectl exec devwebapp-through-service -- curl -s localhost:8080 ; echoThe result displays the secret at the path
secret/data/devwebapp/config.{"password"=>"salsa", "username"=>"giraffe"}
The web application authenticates and requests the secret from the external
Vault server that it found through the external-vault service.
Clean up
Use these steps to clean up the tutorial.
Clean up the minikube instance.
$ minikube delete 🔥 Deleting "minikube" in docker ... 🔥 Deleting container "minikube" ... 🔥 Removing /Users/mrken/.minikube/machines/minikube ... 💀 Removed all traces of the "minikube" cluster.Move up to the parent directory.
$ cd ../Delete the cloned repository.
$ rm -rf learn-vault-external-kubernetes
Next steps
You deployed Vault external to a Kubernetes cluster and deployed pods that leveraged it as a secrets store. First, through a hardcoded network address. Second, aliased behind a Kubernetes service and endpoint.
You can use Helm to deploy the Vault Agent Injector service configured to address an external Vault. See the External Vault section on Vault Helm chart examples for more information.
You can learn more about the Vault Helm chart by reading the documentation, exploring the project source code, exploring how pods can retrieve secrets through the Vault Injector service via annotations, or secrets mounted on ephemeral volumes. Also try using Kubernetes with HCP Vault Dedicated.
You also can consider using the Vault Secrets Operator instead of the sidecar injector to manage static and dynamic secrets. The Vault Secrets Operator allows a Kubernetes developer to Kubernetes Secrets and behind the scenes, Vault handles Secrets Lifecycle Management.