Load Django Secrets From HashiCorp Vault

Use HashiCorp Vault for Django secrets: read KV secrets with hvac using AppRole or Kubernetes auth, render them with Vault Agent or the Vault Secrets Operator, issue dynamic PostgreSQL credentials, and handle lease renewal and restarts.

Problem statement

Organisations that run HashiCorp Vault (or its open-source fork OpenBao) want every application, including Django, to get secrets from it: no secrets in environment files, images, or CI variables, short-lived credentials, and an audit trail. Vault offers several integration styles, from reading a key-value secret at startup to issuing a unique, expiring PostgreSQL user for each app instance. The right choice depends on your platform and how much the app should know about Vault.

Django secrets from VaultApp identityAppRole or k8s SAVault authshort-lived tokenSecretsKV or dynamic DBDjango settingsfiles or envThe app proves its identity, gets a token, and reads only the secrets its policy allows.
  1. Store static secrets (SECRET_KEY, API keys) in the KV v2 engine at a path such as secret/data/myapp/prod.
  2. Give the app an identity: Kubernetes auth (service account) on Kubernetes, AppRole on VMs.
  3. Prefer a sidecar or operator (Vault Agent, Vault Secrets Operator) that renders secrets to files or Kubernetes Secrets, so Django reads files or env vars.
  4. If the app calls Vault itself, use hvac once at startup and cache the values.
  5. For PostgreSQL, consider the database secrets engine: dynamic users with a TTL, renewed by the agent.
  6. Write a Vault policy that grants read on exactly the app's paths.

Step-by-step solution

1. Store secrets and write a policy

vault kv put secret/myapp/prod SECRET_KEY="$(python -c 'import secrets;print(secrets.token_urlsafe(50))')" STRIPE_API_KEY=sk_live_...
# myapp-prod.hcl
path "secret/data/myapp/prod" { capabilities = ["read"] }
path "database/creds/myapp-prod" { capabilities = ["read"] }
vault policy write myapp-prod myapp-prod.hcl

2. Authenticate the app

On Kubernetes, bind a role to the app's service account:

vault write auth/kubernetes/role/myapp-prod \
  bound_service_account_names=django bound_service_account_namespaces=app \
  policies=myapp-prod ttl=1h

On VMs, use AppRole: the deploy tooling delivers a role_id and a wrapped, short-lived secret_id, which the agent exchanges for a token.

3. Render secrets with Vault Agent

Vault Agent runs beside Django, authenticates, and renders templates to files, refreshing them when leases change:

template {
  destination = "/vault/secrets/django.env"
  contents = <<EOT
{{ with secret "secret/data/myapp/prod" }}SECRET_KEY={{ .Data.data.SECRET_KEY }}
STRIPE_API_KEY={{ .Data.data.STRIPE_API_KEY }}{{ end }}
{{ with secret "database/creds/myapp-prod" }}DATABASE_URL=postgres://{{ .Data.username }}:{{ .Data.password }}@db.internal:5432/app?sslmode=require{{ end }}
EOT
}

Django loads the file at startup (django-environ's read_env("/vault/secrets/django.env")). On Kubernetes, the Agent Injector adds this sidecar from pod annotations, or the Vault Secrets Operator syncs values into native Kubernetes Secrets that pods consume with envFrom.

how much should Django know about Vault?Django seesTrade-offVault Secrets Operatorโœ“k8s Secret as envsecret stored in etcd tooAgent sidecar / injectorโœ“rendered fileextra container per podhvac in settingsVault API!app handles auth, renewalKeeping Vault out of application code makes Django portabledjango-deployment.com
Most teams choose the agent or operator so settings stay plain environment reads.

4. Calling Vault from settings with hvac

When no agent is available:

import hvac, os
from pathlib import Path

client = hvac.Client(url=os.environ["VAULT_ADDR"])
jwt = Path("/var/run/secrets/kubernetes.io/serviceaccount/token").read_text()
client.auth.kubernetes.login(role="myapp-prod", jwt=jwt)
data = client.secrets.kv.v2.read_secret_version(path="myapp/prod", mount_point="secret")["data"]["data"]
SECRET_KEY = data["SECRET_KEY"]

This runs once per process at import time. The app now depends on Vault availability at startup and must handle token expiry if it reads secrets later; keep later reads to a minimum.

5. Dynamic database credentials

The database secrets engine creates a PostgreSQL user per lease with a TTL (for example 24 hours). The agent renews the lease while the pod runs; when it ends, Vault drops the user. Django holds connections opened with those credentials; when the agent renders new credentials after a lease is not renewable, restart workers (the agent can run a command after rendering) so new connections use the new user. Grant the dynamic users a role that owns or can access the app's tables, and run migrations with a separate, more privileged role.

one dynamic database credentialPod startsagent authenticatesUser v-myapp-abccreatedTTL 24 hLease renewedwhile pod runsMax TTL reachednew creds renderedWorkers restartedold user revokedEvery pod gets its own short-lived database userdjango-deployment.com
A leaked credential expires on its own, and audit logs show which pod held it.

Verification check: the running pod has no secrets in its spec or image, vault audit logs show reads from the app's role only, and python manage.py check --deploy passes using values from the rendered file or Secret.

Explanation

Vault separates identity from secrets: workloads authenticate with something they already have (a Kubernetes service account token, a cloud instance identity, an AppRole), receive a short-lived token bound to policies, and read only what those policies allow. Agents and operators move authentication and renewal out of the application, so Django keeps reading environment variables or files. Dynamic secrets go further by generating credentials on demand with expiry, which limits the damage of a leak and makes revocation routine.

Edge cases / notes

  • Startup ordering. With the agent injector, use the init container mode so secrets exist before Django starts.
  • OpenBao is API-compatible for these use cases.
  • SECRET_KEY rotation in Vault should use SECRET_KEY_FALLBACKS to avoid logging users out.
  • High availability. If Vault is down, new pods cannot start; run Vault in HA mode and monitor it.

Next steps

FAQ

How does Django read secrets from Vault?

Usually through Vault Agent or the Vault Secrets Operator, which provide files or Kubernetes Secrets. Direct hvac calls also work.

What auth method should Django use with Vault?

Kubernetes auth on Kubernetes, AppRole or a cloud auth method on VMs.

Can Vault give Django temporary database passwords?

Yes, with the database secrets engine, which creates users with a TTL.

Does Vault need to be up for Django to run?

For startup, yes, unless secrets are already synced. Running pods keep working with cached values.

Keep going