chezmoi includes support for HashiCorp Vault using the Vault CLI to expose data as template functions.
Setup
Install Vault CLI
Set the required environment variables:
export VAULT_ADDR = 'https://vault.example.com:8200'
export VAULT_TOKEN = 'your-vault-token'
Or add to your shell profile:
export VAULT_ADDR = 'https://vault.example.com:8200'
export VAULT_TOKEN = 'your-vault-token'
Verify Setup
Test that Vault is configured correctly:
vault kv get -format=json secret/test
Template Function
vault
Get structured data from Vault:
{{ (vault "secret/data/myapp").data.data.password }}
This runs vault kv get -format=json secret/data/myapp and returns parsed JSON.
For KV v2 (default), secrets are stored under secret/data/path. The response structure is {"data": {"data": {"key": "value"}}}.
Usage Examples
Simple Secrets (KV v2)
Store in Vault
Use in Templates
vault kv put secret/github token=ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
vault kv put secret/aws access_key_id=AKIAIOSFODNN7EXAMPLE secret_access_key=wJalrXUtnFEMI/K7MDENG
Database Credentials
Store in Vault
Use in Template
vault kv put secret/database/production \
host=db.example.com \
port= 5432 \
username=app_user \
password=super_secret_password \
database=production_db
Git Configuration
vault kv put secret/git \
name="John Doe" \
email="[email protected] " \
signing_key="0x1234567890ABCDEF"
[user]
name = {{ (vault "secret/data/git").data.data.name }}
email = {{ (vault "secret/data/git").data.data.email }}
signingkey = {{ (vault "secret/data/git").data.data.signing_key }}
AWS Credentials
vault kv put secret/aws/personal \
access_key_id=AKIAIOSFODNN7EXAMPLE \
secret_access_key=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY \
region=us-east-1
vault kv put secret/aws/work \
access_key_id=AKIAI44QH8DHBEXAMPLE \
secret_access_key=je7MtGbClwBF/2Zp9Utk/h3yCo8nvbEXAMPLEKEY \
region=us-west-2
[personal]
aws_access_key_id = {{ (vault "secret/data/aws/personal").data.data.access_key_id }}
aws_secret_access_key = {{ (vault "secret/data/aws/personal").data.data.secret_access_key }}
region = {{ (vault "secret/data/aws/personal").data.data.region }}
[work]
aws_access_key_id = {{ (vault "secret/data/aws/work").data.data.access_key_id }}
aws_secret_access_key = {{ (vault "secret/data/aws/work").data.data.secret_access_key }}
region = {{ (vault "secret/data/aws/work").data.data.region }}
Multiple API Keys
vault kv put secret/api-keys \
github=ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx \
gitlab=glpat-xxxxxxxxxxxxxxxxxxxx \
openai=sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx \
stripe=sk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
~/.config/api-keys.env.tmpl
GITHUB_TOKEN={{ (vault "secret/data/api-keys").data.data.github }}
GITLAB_TOKEN={{ (vault "secret/data/api-keys").data.data.gitlab }}
OPENAI_API_KEY={{ (vault "secret/data/api-keys").data.data.openai }}
STRIPE_SECRET_KEY={{ (vault "secret/data/api-keys").data.data.stripe }}
SSH Configuration
vault kv put secret/ssh/github user=git
vault kv put secret/ssh/gitlab user=git
vault kv put secret/ssh/work-server user=john.doe host=server.company.com
Host github.com
User {{ (vault "secret/data/ssh/github").data.data.user }}
IdentityFile ~/.ssh/id_ed25519
Host gitlab.com
User {{ (vault "secret/data/ssh/gitlab").data.data.user }}
IdentityFile ~/.ssh/id_rsa
Host work-server
HostName {{ (vault "secret/data/ssh/work-server").data.data.host }}
User {{ (vault "secret/data/ssh/work-server").data.data.user }}
IdentityFile ~/.ssh/work_id_rsa
NPM Configuration
vault kv put secret/npm token=npm_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx [email protected]
//registry.npmjs.org/:_authToken={{ (vault "secret/data/npm").data.data.token }}
email={{ (vault "secret/data/npm").data.data.email }}
Vault KV Versions
KV v2 (Default)
KV v2 includes versioning and metadata:
# Write
vault kv put secret/myapp password=secret123
# Read in template
{{ (vault "secret/data/myapp").data.data.password }}
Note the data in the path and nested structure.
KV v1
KV v1 is simpler without versioning:
# Write
vault kv put secret/myapp password=secret123
# Read in template (if using KV v1)
{{ (vault "secret/myapp").data.password }}
Configuration
Custom Command
If vault is not in your PATH:
~/.config/chezmoi/chezmoi.toml
[ vault ]
command = "/custom/path/to/vault"
Environment Variables
Vault CLI respects these environment variables:
export VAULT_ADDR = 'https://vault.example.com:8200'
export VAULT_TOKEN = 'your-vault-token'
export VAULT_NAMESPACE = 'my-namespace' # For Vault Enterprise
export VAULT_SKIP_VERIFY = 'true' # Skip TLS verification (not recommended)
Advanced Usage
Using Vault Namespaces (Enterprise)
export VAULT_NAMESPACE = 'my-namespace'
{{ (vault "secret/data/myapp").data.data.password }}
Dynamic Database Credentials
Vault can generate dynamic database credentials:
# Enable database secrets engine
vault secrets enable database
# Configure
vault write database/config/postgresql \
plugin_name=postgresql-database-plugin \
connection_url="postgresql://{{username}}:{{password}}@localhost:5432/postgres"
# Create role
vault write database/roles/readonly \
db_name=postgresql \
creation_statements="CREATE ROLE \" {{name}} \" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}';" \
default_ttl="1h" \
max_ttl="24h"
# Read dynamic credentials
{{ $dbCreds := vault "database/creds/readonly" }}
username: {{ $dbCreds.data.username }}
password: {{ $dbCreds.data.password }}
Vault Auth Methods
Token Auth
AppRole Auth
Kubernetes Auth
export VAULT_TOKEN = 'your-vault-token'
Simplified Access with Variables
~/.config/app/config.yml.tmpl
{{- $db := (vault "secret/data/database").data.data -}}
{{- $api := (vault "secret/data/api-keys").data.data -}}
database:
host: {{ $db.host }}
port: {{ $db.port }}
username: {{ $db.username }}
password: {{ $db.password }}
api_keys:
github: {{ $api.github }}
gitlab: {{ $api.gitlab }}
openai: {{ $api.openai }}
Complete Examples
Multi-Service Configuration
~/.config/services.yml.tmpl
{{- $github := (vault "secret/data/github").data.data -}}
{{- $aws := (vault "secret/data/aws").data.data -}}
{{- $db := (vault "secret/data/database/production").data.data -}}
github:
token: {{ $github.token }}
username: {{ $github.username }}
aws:
access_key_id: {{ $aws.access_key_id }}
secret_access_key: {{ $aws.secret_access_key }}
region: {{ $aws.region }}
database:
host: {{ $db.host }}
port: {{ $db.port }}
username: {{ $db.username }}
password: {{ $db.password }}
database: {{ $db.database }}
Troubleshooting
Connection Refused
Ensure VAULT_ADDR is set correctly:
echo $VAULT_ADDR
vault status
Permission Denied
Verify your token has the required policies:
vault token lookup
vault policy read my-policy
Secret Not Found
List secrets to verify the path:
vault kv list secret/
vault kv get secret/data/myapp
TLS Certificate Errors
For development, you can skip TLS verification (not recommended for production):
export VAULT_SKIP_VERIFY = true
Testing Templates
Test template functions:
chezmoi execute-template '{{ (vault "secret/data/test").data.data.value }}'
Verify Vault CLI
which vault
vault --version
vault status
Best Practices
Use least privilege : Grant minimal required permissions via policies
Rotate tokens : Use short-lived tokens and rotate regularly
Use namespaces : Organize secrets with namespaces (Enterprise)
Enable audit logging : Track all secret access
Use dynamic secrets : Leverage Vault’s dynamic secret generation
Secure communication : Always use TLS in production
Use auth methods : Prefer AppRole or Kubernetes auth over tokens
Organize paths : Use a clear hierarchy for secret paths
Version secrets : Use KV v2 for secret versioning
Monitor access : Set up alerts for suspicious access patterns
See Also