Hardcoded secrets are the vulnerability that never dies. They show up in Git history, in environment variables dumped to logs, in Slack messages, and in that one config file everyone forgot about. HashiCorp Vault flips the model: secrets are centralized, access-controlled, audited, and, crucially, short-lived.
The core idea: stop storing static secrets
The most powerful Vault feature is not the encrypted key-value store. It is dynamic secrets. Instead of a database password that lives forever, an application asks Vault for credentials on startup. Vault creates a brand-new database user with a TTL of, say, one hour, hands it back, and revokes it automatically when the lease expires.
The blast radius of a leaked credential drops from "forever" to "one hour", and every issuance is logged.
Authentication: who gets to ask?
Vault does not care about your password. It cares about verifiable identity. In Kubernetes, the kubernetes auth method validates a pod service-account token against the cluster API, then maps it to a Vault policy.
# Enable the Kubernetes auth method
vault auth enable kubernetes
vault write auth/kubernetes/config \
kubernetes_host="https://$KUBERNETES_PORT_443_TCP_ADDR:443"
# Bind a service account to a policy
vault write auth/kubernetes/role/orders-api \
bound_service_account_names=orders-api \
bound_service_account_namespaces=production \
policies=orders-db \
ttl=1hA least-privilege policy
Policies are deny-by-default. Grant only the exact paths a workload needs:
# orders-db policy
path "database/creds/orders-readwrite" {
capabilities = ["read"]
}
path "secret/data/orders/*" {
capabilities = ["read"]
}Dynamic database credentials in action
# Configure the database secrets engine once
vault secrets enable database
vault write database/config/orders \
plugin_name=postgresql-database-plugin \
connection_url="postgresql://{{username}}:{{password}}@db:5432/orders" \
allowed_roles="orders-readwrite"
vault write database/roles/orders-readwrite \
db_name=orders \
creation_statements="CREATE ROLE "{{name}}" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}'; GRANT SELECT, INSERT, UPDATE ON ALL TABLES IN SCHEMA public TO "{{name}}";" \
default_ttl=1h max_ttl=24hNow vault read database/creds/orders-readwrite returns a fresh, expiring username and password. The application never sees a permanent credential.
Getting secrets into pods without app changes
The Vault Agent Injector uses a mutating webhook to add a sidecar that authenticates and writes secrets to a shared in-memory volume. Annotations do the wiring:
apiVersion: apps/v1
kind: Deployment
metadata:
name: orders-api
spec:
template:
metadata:
annotations:
vault.hashicorp.com/agent-inject: "true"
vault.hashicorp.com/role: "orders-api"
vault.hashicorp.com/agent-inject-secret-db: "database/creds/orders-readwrite"
spec:
serviceAccountName: orders-apiOperational guardrails
Seal/unseal: Vault starts sealed. Use auto-unseal with a cloud KMS so a restart does not page someone at 3am.
Audit devices: Enable audit logging before production. Every request is logged, which is your forensic trail.
Rotate the root token: Generate it, do the initial setup, then revoke it. Day-to-day work uses scoped tokens.
Lease monitoring: Watch for workloads that never renew, a sign of a broken integration hoarding credentials.
Vault is not a place to dump passwords. It is a system for making static, long-lived secrets disappear. Start with one high-value database, prove the dynamic-secrets loop, and expand from there.



