What is HashiCorp Vault?
Most applications need secrets - database passwords, API keys, TLS certificates, cloud provider credentials. The naive approach is to store them in environment variables or .env files committed to a repository. That approach has a long and painful history of incidents. HashiCorp Vault is the production answer.
Vault is a secrets management platform that provides a single source of truth for sensitive configuration values. It encrypts secrets at rest and in transit, enforces fine-grained access policies, generates an audit log of every read and write, and - uniquely - can generate credentials dynamically rather than requiring you to manage long-lived static secrets at all.
Core Concepts
Secrets Engines: Vault organises secrets into engines, each of which handles a different type of credential. The KV (Key-Value) engine stores arbitrary static secrets. The database engine generates short-lived database credentials. The AWS secrets engine generates temporary IAM credentials. The PKI engine acts as a certificate authority. Each engine is mounted at a path in Vault's namespace.
Auth Methods: Vault needs to verify who is asking for a secret before returning it. Auth methods handle this authentication step. Common methods include AppRole (for automated systems), Kubernetes (for pods running in a cluster), AWS IAM (for EC2 instances and Lambda), and OIDC/JWT (for humans via SSO).
Policies: Once authenticated, Vault determines what a client can access through HCL policies. Policies attach to tokens and define read/write/list permissions on specific paths.
Leases and TTLs: Every secret issued by Vault has a lease - a time-to-live. When the lease expires, the secret is revoked. Dynamic secrets can have TTLs as short as minutes, dramatically reducing the window of exposure if a credential is compromised.
A Vault Policy and CLI Workflow
# policy: api-server-policy.hcl
# Allow the API server to read its database credentials
path "database/creds/api-server-role" {
capabilities = ["read"]
}
# Allow reading static application secrets
path "secret/data/api-server/*" {
capabilities = ["read", "list"]
}
# Allow renewing its own token
path "auth/token/renew-self" {
capabilities = ["update"]
}
# Write the policy to Vault
vault policy write api-server api-server-policy.hcl
# Enable the database secrets engine
vault secrets enable database
# Configure a Postgres connection
vault write database/config/app-postgres \
plugin_name=postgresql-database-plugin \
connection_url="postgresql://{{username}}:{{password}}@db.internal:5432/appdb" \
allowed_roles="api-server-role" \
username="vault-admin" \
password="$VAULT_ADMIN_PASSWORD"
# Create a dynamic role with a 1-hour TTL
vault write database/roles/api-server-role \
db_name=app-postgres \
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="4h"
# An application can now request short-lived credentials
vault read database/creds/api-server-role
# Key Value
# username v-appserver-xKq2-1748291204
# password A1a-s8Yz...
# lease_duration 1h
The username and password above are unique to this request. Vault will delete that Postgres user after one hour. If these credentials are leaked, the blast radius is limited to one hour and one user.
AppRole Authentication for CI/CD
AppRole is the standard authentication method for CI/CD pipelines and automated systems. The pipeline authenticates with a Role ID and a Secret ID to receive a short-lived Vault token:
# Retrieve a Vault token using AppRole credentials
VAULT_TOKEN=$(vault write -field=token auth/approle/login \
role_id="$VAULT_ROLE_ID" \
secret_id="$VAULT_SECRET_ID")
# Use the token to fetch a secret
VAULT_TOKEN=$VAULT_TOKEN vault kv get -field=value secret/api-server/stripe-key
In a CI/CD context, the Role ID is stored as a non-sensitive pipeline variable, and the Secret ID is injected at runtime from a secure pipeline secret store. Secret IDs can be single-use - they are consumed on first login and cannot be replayed.
Vault Agent in Kubernetes
Vault Agent runs as a sidecar or init container alongside application pods in Kubernetes. It handles the Vault authentication flow and writes secrets to a shared volume or injects them as environment variables. The application reads a file - it never needs to contain any Vault SDK code:
# Kubernetes deployment with Vault Agent injection
# (using the Vault Agent Injector, installed via Helm)
metadata:
annotations:
vault.hashicorp.com/agent-inject: "true"
vault.hashicorp.com/role: "api-server"
vault.hashicorp.com/agent-inject-secret-db-creds: "database/creds/api-server-role"
vault.hashicorp.com/agent-inject-template-db-creds: |
{{- with secret "database/creds/api-server-role" -}}
DATABASE_URL=postgresql://{{ .Data.username }}:{{ .Data.password }}@db.internal/appdb
{{- end }}
Vault Agent renders the secret into /vault/secrets/db-creds and keeps it renewed as the lease approaches expiry. This is the DevSecOps standard for secret delivery in containerised workloads.
