Kubernetes doesn’t actually inject secrets into pods; it mounts them as files.

Let’s see Vault’s dynamic secrets in action with Kubernetes.

Imagine you have a Kubernetes cluster and you want your pods to automatically get credentials for external services like a database or an API, without you having to manually create Kubernetes Secret objects or bake credentials into your container images. This is where Vault’s dynamic secrets and its Kubernetes integration shine.

Here’s a simplified workflow:

  1. Vault is configured to authenticate with your Kubernetes cluster. It trusts your cluster’s service account tokens.
  2. Your Kubernetes pod is annotated. You tell Vault, "Hey, when this pod starts, I need credentials for X service."
  3. Vault receives the request (via a mutating webhook or a sidecar). It generates new, short-lived credentials for the requested service (e.g., a PostgreSQL user and password).
  4. Vault injects these credentials into the pod.

Let’s get more concrete. We’ll use Vault’s kubernetes auth method and its database secrets engine.

Prerequisites:

  • A running Kubernetes cluster.
  • A running Vault server, accessible from your cluster.
  • Vault’s kubernetes auth method enabled and configured to trust your cluster’s service account.
  • Vault’s database secrets engine enabled (e.g., for PostgreSQL).

Step 1: Configure Vault Kubernetes Auth

First, you need to tell Vault about your Kubernetes cluster. This usually involves pointing Vault at the Kubernetes API server and providing a Kubernetes role that defines which service accounts are allowed to authenticate.

# Example: Enable and configure Kubernetes auth method
vault auth enable kubernetes

vault write auth/kubernetes/config \
    kubernetes_host="https://192.168.1.100:6443" \
    kubernetes_ca_cert=@/path/to/ca.crt \
    token_reviewer_jwt=$(cat /path/to/sa_token) # This is a simplified example, better to use service account discovery

# Example: Create a Kubernetes role in Vault
vault write auth/kubernetes/role/my-app-role \
    bound_service_account_names=my-app-sa \
    bound_service_account_namespaces=default \
    policies=my-app-policy \
    ttl=20m

Step 2: Configure Vault Database Secrets Engine

Next, configure Vault to generate dynamic database credentials.

# Example: Enable PostgreSQL secrets engine
vault secrets enable database

# Example: Configure connection to your PostgreSQL database
vault write database/config/my-pg-db \
    plugin_name=postgresql \
    allowed_roles=my-app-db-role \
    connection_url="postgresql://vault:vaultpassword@host.docker.internal:5432/mydatabase?sslmode=disable" \
    username="vault" \
    password="vaultpassword"

Step 3: Create a Kubernetes Role in Vault for Database Access

This role in Vault links the Kubernetes role (my-app-role) to the database secrets engine role (my-app-db-role).

# Example: Create a database role in Vault
vault write database/roles/my-app-db-role \
    db_name=my-pg-db \

    creation_statements="CREATE USER '{{name}}'@'%' IDENTIFIED BY '{{password}}'; GRANT SELECT,INSERT,UPDATE,DELETE ON mydatabase.* TO '{{name}}'@'%';" \

    default_ttl=10m \
    max_ttl=30m

Step 4: Deploy your Application Pod with Annotations

Now, annotate your pod to request secrets. Vault’s kubernetes-secrets-init or a mutating webhook will handle the rest. For simplicity, let’s assume you’re using the kubernetes-secrets-init sidecar pattern.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
  namespace: default
spec:
  replicas: 1
  selector:
    matchLabels:
      app: my-app
  template:
    metadata:
      labels:
        app: my-app
      annotations:
        vault.hashicorp.com/agent-inject: "true"
        vault.hashicorp.com/role: "my-app-role" # This is the Vault Kubernetes role
        vault.hashicorp.com/agent-inject-secret-myappdb.txt: "database/creds/my-app-db-role" # Request dynamic DB creds
        vault.hashicorp.com/agent-inject-template-myappdb.txt: |

          {{- with secret "database/creds/my-app-db-role" -}}


          export DB_USER="{{ .Data.username }}"


          export DB_PASSWORD="{{ .Data.password }}"


          {{- end -}}

    spec:
      serviceAccountName: my-app-sa # Ensure this SA exists and is linked to Vault role
      containers:
      - name: my-app
        image: your-app-image:latest
        env:
          # These will be populated by the inject-template
          - name: DB_USER
            valueFrom:
              secretKeyRef:
                name: vault-token # Placeholder, actual values come from template
                key: DB_USER
          - name: DB_PASSWORD
            valueFrom:
              secretKeyRef:
                name: vault-token # Placeholder
                key: DB_PASSWORD

When this pod starts, the Vault agent (often injected as a sidecar or init container) will:

  1. Use the pod’s service account token to authenticate with Vault.
  2. Request dynamic credentials from the database/creds/my-app-db-role path.
  3. Vault generates a new PostgreSQL user and password.
  4. The agent-inject-template processes these credentials. It writes them to a file named myappdb.txt within the pod’s filesystem (e.g., /vault/secrets/myappdb.txt).
  5. The template also sets environment variables DB_USER and DB_PASSWORD using the values from the generated secret.

Your application can then read /vault/secrets/myappdb.txt or use the DB_USER and DB_PASSWORD environment variables to connect to your PostgreSQL database. The credentials are automatically rotated by Vault based on the configured TTLs.

The truly surprising thing is that many users think of this as a "secret injection" mechanism when, at its core, it’s a sophisticated credential rotation and management system that happens to integrate with Kubernetes to deliver those credentials. The "injection" part is just how the credentials are made available to the application.

The most common pattern is using an init container (vault-agent-init) or a sidecar (vault-agent) that runs before or alongside your application container. This agent is responsible for authenticating to Vault using the pod’s Service Account token, retrieving secrets based on annotations, and then making them available to the application container.

This setup allows for a robust security posture: no static secrets in Kubernetes, no secrets in container images, and dynamic, short-lived credentials for every application instance.

The vault.hashicorp.com/agent-inject-template-myappdb.txt field is where the magic of templating happens. It’s not just about fetching a secret; it’s about transforming that secret into a format your application can easily consume. Here, we’re taking the raw username and password from Vault’s database secrets engine and exporting them as environment variables, making them directly usable by the application without any code changes. This templating engine is Go’s text/template package, giving you immense flexibility to format secrets, render configuration files, or even generate scripts.

The next concept you’ll likely encounter is managing the lifecycle of these injected secrets, particularly how your application handles credential rotation before Vault’s TTL expires, and what happens if the Vault agent fails to renew or retrieve new credentials.

Want structured learning?

Take the full Vault course →