Get secrets out of Vault and into your application and pipelines — environment injection, the AppRole login flow in CI, Vault Agent, and the production seal you must understand.
Why: an app never hardcodes secrets — it authenticates to Vault at startup, reads what it needs, and uses short-lived tokens. The flow is always: authenticate (AppRole/Kubernetes/cloud IAM) → receive a scoped token → read secrets → renew or re-auth as tokens expire. Everything in this course feeds this loop.
startup ─▶ authenticate (AppRole) ─▶ token ─▶ read secret/dynamic creds
▲ │
└────────── token expires, re-auth ◀───────┘Why: the simplest integration reads a secret in a shell script and exports it as an environment variable the app picks up. -field extracts one value; -format=json feeds a parser like jq. Keep the value in the environment, never written to a file or logged.
export VAULT_ADDR='http://127.0.0.1:8200'Pull a single field into an environment variable
export DB_PASSWORD=$(vault kv get -field=password secret/myapp)Or get JSON and parse it
vault kv get -format=json secret/myapp | jq -r '.data.data'Why: a pipeline authenticates with AppRole. The role_id and secret_id come from the CI secret store (never the repo), are exchanged for a token, and that token reads the secrets the job needs. Because the token is short-lived and scoped, a leaked pipeline log ages out fast.
In the pipeline (role_id/secret_id injected from CI secrets):
VAULT_TOKEN=$(vault write -field=token auth/approle/login \
role_id="$ROLE_ID" secret_id="$SECRET_ID")export VAULT_TOKENNow read what the job needs
vault kv get -field=api_key secret/ci/deployWhy: doing auth, renewal, and secret-fetching by hand is fiddly. Vault Agent is a sidecar that logs in automatically (auto-auth), keeps the token renewed, and can render secrets into config files from templates — so your app just reads a normal file. It removes Vault-specific code from the application.
# vault-agent.hcl (sketch)
auto_auth {
method "approle" {
config = {
role_id_file_path = "/etc/vault/role_id"
secret_id_file_path = "/etc/vault/secret_id"
}
}
}
template {
destination = "/etc/app/db.env"
contents = "DB_PASSWORD={{ with secret \"secret/data/myapp\" }}{{ .Data.data.password }}{{ end }}"
}Note: the dev server hid this, but real Vault starts SEALED — its data is encrypted and unreadable until unsealed with a quorum of key shares (or auto-unseal via a cloud KMS). This is the master safeguard: a stolen Vault disk is useless while sealed. Production also means a real storage backend, TLS, audit devices, and high availability — beyond a dev box, but the same commands you have learned apply.
start ─▶ SEALED (data encrypted, unreadable)
│ provide quorum of unseal keys (e.g. 3 of 5) — or auto-unseal via KMS
▼
UNSEALED ─▶ serves requests
(production also adds: real storage, TLS, audit log, HA — dev mode skips all this)