Usage guide¶
This guide covers how to configure Pydantic settings models, annotate fields for Vault lookups, set environment variables, and prepare Vault policies for pydantic2-settings-vault.
For KV path conventions and policy examples by engine version, see Vault KV & policies. For auth method details, see Authentication. For client tuning, cache, and validation, see Advanced configuration.
Quick start¶
- Install the package:
- Define a settings model with Vault-backed fields and register the settings source:
from pydantic import Field, SecretStr
from pydantic_settings import BaseSettings, PydanticBaseSettingsSource
from pydantic2_settings_vault import VaultConfigSettingsSource
class AppSettings(BaseSettings):
API_KEY: SecretStr = Field(
...,
json_schema_extra={
"vault_secret_path": "secret/myapp/config",
"vault_secret_key": "api_key",
},
)
@classmethod
def settings_customise_sources(
cls,
settings_cls,
init_settings,
env_settings,
dotenv_settings,
file_secret_settings,
):
return (
init_settings,
env_settings,
dotenv_settings,
VaultConfigSettingsSource(settings_cls=settings_cls),
)
- Configure Vault authentication (AppRole is the default):
export VAULT_URL="https://vault.example.com:8200"
export VAULT_ROLE_ID="<role-id>"
export VAULT_SECRET_ID="<secret-id>"
- Write the secret in Vault (KV v2 mount
secret):
- Load settings:
Field annotation patterns¶
Opt-in Vault fields¶
Only fields that include json_schema_extra with Vault metadata are fetched from Vault. Other fields behave like normal pydantic-settings fields (environment variables, .env files, init kwargs, and so on).
class AppSettings(BaseSettings):
# Loaded from Vault
DB_PASSWORD: SecretStr = Field(
...,
json_schema_extra={
"vault_secret_path": "secret/myapp/database",
"vault_secret_key": "password",
},
)
# Loaded from environment (e.g. DB_HOST=localhost)
DB_HOST: str = "localhost"
Required metadata¶
Every Vault-backed field must include both keys in json_schema_extra:
| Key | Description |
|---|---|
vault_secret_path |
Logical Vault path (KV v2) or full API path. See Path conventions. |
vault_secret_key |
Key inside the secret payload returned by Vault. |
If either key is missing, settings load fails with a clear error naming the field and the missing metadata.
Field types¶
Use SecretStr for sensitive values so they are not printed in logs or tracebacks by default:
Non-secret configuration values can use any Pydantic-supported type. Vault returns string values; Pydantic coerces them during validation:
MAX_CONNECTIONS: int = Field(
...,
json_schema_extra={
"vault_secret_path": "secret/myapp/config",
"vault_secret_key": "max_connections",
},
)
Store "100" in Vault; Pydantic validates and converts it to int.
Group secrets by path¶
Map several fields to the same vault_secret_path with different vault_secret_key values. The source fetches each unique path once per settings load:
class AppSettings(BaseSettings):
DB_HOST: str = Field(
...,
json_schema_extra={
"vault_secret_path": "secret/myapp/database",
"vault_secret_key": "host",
},
)
DB_PASSWORD: SecretStr = Field(
...,
json_schema_extra={
"vault_secret_path": "secret/myapp/database",
"vault_secret_key": "password",
},
)
DB_PORT: int = Field(
...,
json_schema_extra={
"vault_secret_path": "secret/myapp/database",
"vault_secret_key": "port",
},
)
Write all keys in one Vault secret:
Per-field KV engine version¶
Set the default KV version globally with VAULT_KV_VERSION (2 by default). Override on individual fields when reading legacy KV v1 mounts:
LEGACY_TOKEN: SecretStr = Field(
...,
json_schema_extra={
"vault_secret_path": "legacy/myapp/token",
"vault_secret_key": "value",
"vault_kv_version": 1,
},
)
Settings source order¶
Register VaultConfigSettingsSource last in settings_customise_sources so explicit init values and environment variables take precedence over Vault:
return (
init_settings, # highest priority
env_settings,
dotenv_settings,
file_secret_settings,
VaultConfigSettingsSource(settings_cls=settings_cls), # lowest priority
)
Reusable annotation helper¶
To avoid repeating path metadata, define a small factory in your application:
from typing import Any
from pydantic import Field
def vault_field(path: str, key: str, **field_kwargs: Any):
return Field(
...,
json_schema_extra={
"vault_secret_path": path,
"vault_secret_key": key,
},
**field_kwargs,
)
class AppSettings(BaseSettings):
API_KEY: SecretStr = vault_field("secret/myapp/config", "api_key")
End-to-end configuration examples¶
Local development with AppRole¶
Run Vault locally (Docker example):
docker run --cap-add=IPC_LOCK -e 'VAULT_DEV_ROOT_TOKEN=root' -p 8200:8200 hashicorp/vault server -dev
export VAULT_ADDR=http://127.0.0.1:8200
export VAULT_TOKEN=root
Enable the KV v2 engine, write a secret, create a read policy, and configure AppRole:
vault secrets enable -path=secret kv-v2
vault kv put -mount=secret myapp/config api_key="dev-key"
vault policy write myapp-read - <<EOF
path "secret/data/myapp/*" {
capabilities = ["read"]
}
path "auth/approle/login" {
capabilities = ["create", "update"]
}
EOF
vault auth enable approle
vault write auth/approle/role/myapp token_policies="myapp-read"
vault write auth/approle/role/myapp/secret-id
export VAULT_ROLE_ID=$(vault read -field=role_id auth/approle/role/myapp/role-id)
export VAULT_SECRET_ID=$(vault write -f -field=secret_id auth/approle/role/myapp/secret-id)
export VAULT_URL=http://127.0.0.1:8200
Use a cached settings accessor for repeated lookups in development:
from functools import lru_cache
from threading import Lock
app_settings_lock = Lock()
@lru_cache
def get_app_settings() -> AppSettings:
with app_settings_lock:
return AppSettings() # type: ignore[call-arg]
Kubernetes deployment¶
Use the Kubernetes auth method and a service-account JWT:
import os
os.environ["VAULT_AUTH_METHOD"] = "kubernetes"
os.environ["VAULT_K8S_ROLE"] = "myapp"
os.environ["VAULT_URL"] = "https://vault.example.com:8200"
# JWT is read from /var/run/secrets/kubernetes.io/serviceaccount/token by default
Vault policy for the role should allow read on application secret paths and login on the Kubernetes auth mount. Example:
path "secret/data/myapp/*" {
capabilities = ["read"]
}
path "auth/kubernetes/login" {
capabilities = ["create", "update"]
}
Bind the Kubernetes auth role to the pod service account:
vault write auth/kubernetes/role/myapp \
bound_service_account_names=myapp \
bound_service_account_namespaces=production \
policies=myapp-read \
ttl=1h
Production startup with validation¶
Validate configuration before serving traffic. Use a dedicated settings class or factory when you need production client tuning:
from pydantic2_settings_vault import (
VaultClientConfig,
VaultConfigSettingsSource,
validate_vault_configuration,
)
class ProductionAppSettings(AppSettings):
@classmethod
def settings_customise_sources(
cls,
settings_cls,
init_settings,
env_settings,
dotenv_settings,
file_secret_settings,
):
return (
init_settings,
env_settings,
dotenv_settings,
VaultConfigSettingsSource(
settings_cls=settings_cls,
client_config=VaultClientConfig.for_production(),
cache_enabled=True,
cache_ttl_seconds=300,
),
)
def bootstrap_settings() -> ProductionAppSettings:
validate_vault_configuration(
ProductionAppSettings,
check_auth=True, # dry-run login against Vault
).raise_if_invalid()
return ProductionAppSettings() # type: ignore[call-arg]
Run the same validation in CI without loading secrets:
# Fails fast on missing env vars or incomplete field metadata; no secret fetch
validate_vault_configuration(AppSettings).raise_if_invalid()
Mixed configuration sources¶
Combine Vault secrets with environment-driven non-secret settings:
from pydantic_settings import SettingsConfigDict
class AppSettings(BaseSettings):
model_config = SettingsConfigDict(env_prefix="MYAPP_")
# From environment: MYAPP_ENV=production
ENV: str = "development"
# From Vault
API_KEY: SecretStr = Field(
...,
json_schema_extra={
"vault_secret_path": "secret/myapp/config",
"vault_secret_key": "api_key",
},
)
Path layout can include the environment segment for policy scoping:
Prefer distinct paths per environment (secret/prod/..., secret/staging/...) rather than sharing production secrets across environments.
Environment variables¶
Common variables (all auth methods)¶
| Variable | Required | Default | Description |
|---|---|---|---|
VAULT_URL |
No | http://127.0.0.1:8200 |
Vault API base URL |
VAULT_AUTH_METHOD |
No | approle |
Authentication backend to use |
VAULT_AUTH_MOUNT |
No | method name | Auth mount path override (e.g. kubernetes → auth/kubernetes/login) |
VAULT_NAMESPACE |
No | — | HashiCorp Vault Enterprise namespace (X-Vault-Namespace header) |
VAULT_KV_VERSION |
No | 2 |
Default KV engine version (1 or 2) |
AppRole (default)¶
| Variable | Required |
|---|---|
VAULT_ROLE_ID |
Yes |
VAULT_SECRET_ID |
Yes |
Other auth methods¶
Token, Kubernetes, AWS, GCP, Azure, JWT, OIDC, Cert, LDAP, OCI, Userpass, GitHub, Okta, Kerberos, RADIUS, Alicloud, CF, and PCF each require method-specific variables. See the Authentication guide for the full reference.
Optional client tuning¶
VaultClientConfig is passed to VaultConfigSettingsSource in code, not via environment variables. Presets:
| Preset | Timeout | Concurrency | Retries | Backoff |
|---|---|---|---|---|
VaultClientConfig.for_local() |
60s | 3 | 3 | 0.2s → 1.0s |
VaultClientConfig.for_ci() |
15s | 5 | 2 | 0.05s → 0.2s |
VaultClientConfig.for_production() |
30s | 10 | 5 | 0.1s → 2.0s |
Recommended Vault policies¶
Grant the narrowest read access required. Examples assume AppRole at mount approle; adjust auth paths for your method.
KV v2 application policy¶
# Read application secrets only
path "secret/data/myapp/*" {
capabilities = ["read"]
}
# Allow AppRole login
path "auth/approle/login" {
capabilities = ["create", "update"]
}
Create and attach the policy:
vault policy write myapp-read - <<EOF
path "secret/data/myapp/*" {
capabilities = ["read"]
}
path "auth/approle/login" {
capabilities = ["create", "update"]
}
EOF
vault write auth/approle/role/myapp token_policies="myapp-read"
AppRole setup checklist¶
- Enable AppRole:
vault auth enable approle - Write a policy with read on secret paths and login on
auth/approle/login - Create a role:
vault write auth/approle/role/myapp token_policies="myapp-read" - Distribute
role_id(can be wrapped) and generatesecret_idper deployment - Set
VAULT_ROLE_ID,VAULT_SECRET_ID, andVAULT_URLin the runtime environment
Enterprise namespaces¶
When using VAULT_NAMESPACE, define policies inside that namespace. The library sends X-Vault-Namespace on authentication and secret reads automatically.
KV v1 and mixed engines¶
See vault-kv-and-policies.md for KV v1 paths, mixed-engine policies, and field-mapping patterns.
Troubleshooting¶
| Symptom | Likely cause | Action |
|---|---|---|
Missing required Vault environment variables |
Auth credentials not set | Set method-specific vars; run validate_vault_configuration |
Vault metadata for settings field 'X' is incomplete |
Missing path or key in json_schema_extra |
Add both vault_secret_path and vault_secret_key |
Vault secret key 'X' ... was not found at Vault path 'Y' |
Key missing in Vault payload | Fix secret content or vault_secret_key name |
Pydantic ValidationError after Vault fetch |
Type coercion failed | Store a value compatible with the field type in Vault |
| HTTP 403 on secret read | Policy too narrow | Extend policy to cover secret/data/<path> (KV v2) |
Sensitive values are never included in log messages or exception text.
Related documentation¶
- Authentication — all supported Vault auth methods
- Advanced configuration — client controls, cache, validation API
- Vault KV & policies — KV v1/v2 paths, policies, field-mapping patterns