Secrets management done right. Every other tutorial warns you never to commit secrets — Vault is where they go instead. You'll run Vault locally, store and read secrets, understand the security model, generate short-lived dynamic credentials, and adopt the patterns that keep secrets out of Git, images, and env files. Work top to bottom, or jump to a part.
The journey:
| You need | Why |
|---|---|
The vault CLI installed |
Runs every command here |
| A terminal | All CLI |
| Basic secrets/auth familiarity | Tokens, policies, and env vars come up throughout |
No cloud account is required for the dev server. Confirm the CLI: vault version.
Secrets sprawl: passwords in .env files, API keys in CI config, database creds baked into images, private keys emailed around. Each is a leak waiting to happen, and none can be rotated quickly.
Vault centralizes secrets behind an authenticated, audited API and adds three things a .env file can't:
apps / humans / CI → authenticate → Vault → static + dynamic secrets (audited)
The dev server is in-memory, auto-unsealed, and not for production — perfect for learning:
vault server -dev
It prints a Root Token and an Unseal Key. In another terminal:
export VAULT_ADDR='http://127.0.0.1:8200'
export VAULT_TOKEN='<root-token-from-output>'
vault status # sealed? initialized? HA?
VAULT_ADDR tells the CLI where the server is; VAULT_TOKEN is your current identity. Everything you do is authenticated by that token.
Three concepts unlock everything:
secret/, database/), and policies grant access per path. vault path-help <path> explains any mount.vault secrets list # what engines are mounted where
vault token lookup # info about your current token
The KV (key/value) engine stores secrets you bring:
# Write a secret (KV v2 uses the 'kv' subcommand)
vault kv put secret/myapp/db username="app" password="s3cr3t"
# Read it back
vault kv get secret/myapp/db
vault kv get -field=password secret/myapp/db # just one field, for scripts
# Versioned: KV v2 keeps history
vault kv get -version=1 secret/myapp/db
vault kv delete secret/myapp/db # soft-delete (recoverable)
KV v2 versions every write, so you can roll back and recover deleted secrets. This replaces the .env file — apps read from Vault at startup instead of shipping secrets in the image.
A policy is HCL granting capabilities on paths. Least privilege is the whole point:
# myapp-policy.hcl
path "secret/data/myapp/*" {
capabilities = ["read"]
}
path "secret/data/shared/config" {
capabilities = ["read", "list"]
}
vault policy write myapp myapp-policy.hcl
vault token create -policy=myapp # a token that can ONLY read myapp secrets
Capabilities are create, read, update, delete, list, deny. Note the data/ segment — KV v2 API paths insert it, a classic gotcha when a policy "doesn't work." Grant each app only the paths it needs, nothing more.
Root tokens are for setup only. Real principals authenticate via an auth method that maps an identity to policies and hands back a short-lived token:
| Method | For |
|---|---|
| AppRole | Apps/CI — a role id + secret id exchanged for a token |
| Kubernetes | Pods authenticate with their ServiceAccount JWT |
| AWS/GCP/Azure | Workloads prove cloud identity |
| OIDC/LDAP | Humans via SSO |
vault auth enable approle
vault write auth/approle/role/myapp policies="myapp" token_ttl=1h
vault read auth/approle/role/myapp/role-id
vault write -f auth/approle/role/myapp/secret-id
# the app exchanges role-id + secret-id → a token scoped to the myapp policy
The pattern: no human ever hands a long-lived secret to an app — the app proves what it is (a K8s pod, an EC2 instance) and Vault issues a scoped, expiring token.
Instead of storing a database password, Vault can create a brand-new one on demand and delete it when the lease expires:
vault secrets enable database
vault write database/config/mydb \
plugin_name=postgresql-database-plugin \
connection_url="postgresql://{{username}}:{{password}}@localhost:5432/app" \
allowed_roles="readonly" username="vaultadmin" password="..."
vault write database/roles/readonly \
db_name=mydb default_ttl=1h max_ttl=24h \
creation_statements="CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}'; GRANT SELECT ON ALL TABLES IN SCHEMA public TO \"{{name}}\";"
# Each call returns a fresh, expiring DB credential:
vault read database/creds/readonly
Now no static DB password exists to leak. A credential lives an hour, then Vault revokes it in the database automatically. The same works for cloud IAM creds, SSH, PKI certificates, and more — short-lived by default flips secrets from a standing liability to a transient one.
The Transit engine encrypts data without ever storing it — your app sends plaintext, Vault returns ciphertext, and the key never leaves Vault:
vault secrets enable transit
vault write -f transit/keys/orders
vault write transit/encrypt/orders plaintext=$(echo -n "4111-1111" | base64)
# → ciphertext: vault:v1:...
vault write transit/decrypt/orders ciphertext="vault:v1:..."
This gives you app-layer encryption with central key management and rotation — you never handle raw keys, and rotating a key doesn't require re-encrypting old data (versioned ciphertext).
-dev in production. Use a real storage backend (Integrated Storage/Raft), TLS, and proper unseal (auto-unseal via cloud KMS, or Shamir with a real quorum).data/ path gotcha. API paths insert data/ (secret/data/myapp/* in policies, not secret/myapp/*). This breaks more policies than anything else.path "*" { capabilities = ["read"] } defeats the purpose. Scope tightly.secret/myapp/db, then create a policy that can only read secret/data/myapp/* and a token bound to it. Prove the token can't read another path.myapp, fetch its role-id/secret-id, and exchange them for a scoped token.database/creds/readonly twice — confirm you get two different, expiring users.Self-check:
data/ segment come from, and why does it break policies?You now have the full loop: run → seal model → store static → scope with policies → authenticate apps → generate dynamic secrets → encrypt → harden. That's secrets management the way secure teams actually do it.
Learned the concepts? Test yourself with Vault interview questions.
Go to the question bank →