Templates¶
Templates let you reshape Vault secret data before it is written to a target file. They use Go's text/template syntax and receive the Vault KV data map as the dot (.) context.
When no template is specified, the raw Vault secret data is passed directly to the format handler.
Template functions¶
In addition to the standard Go template functions, dotvault provides:
| Function | Signature | Description |
|---|---|---|
env |
env(key) |
Look up an environment variable |
base64encode |
base64encode(s) |
Base64-encode a string |
base64decode |
base64decode(s) |
Base64-decode a string |
default |
default(fallback, val) |
Return val if non-empty, otherwise fallback |
quote |
quote(s) |
Shell-safe single quoting |
username |
username |
The local OS account the secrets are synced under |
The default function follows the Sprig convention where the fallback comes first, enabling idiomatic piping:
The username function returns the OS account dotvault runs as — the same identity the kv/users/<username>/… path layout is built from (DOMAIN\ prefix stripped on Windows). It is a function, not a field, so it is always available regardless of the secret's contents and never collides with a secret field that happens to be named user. Use it to build per-user filesystem paths without storing the username in Vault:
Examples by format¶
YAML¶
Sync a GitHub CLI credential into ~/.config/gh/hosts.yml:
rules:
- name: gh
vault_key: "gh"
target:
path: "~/.config/gh/hosts.yml"
format: yaml
template: |
github.com:
oauth_token: "{{ .oauth_token }}"
user: "{{ .user }}"
git_protocol: https
If the user already has other entries in hosts.yml (e.g. for GitHub Enterprise), those entries are preserved. Only the github.com key is merged.
Vault secret (kv/data/users/jane/gh):
Result (merged with existing file):
github.com:
oauth_token: "ghp_xxxx"
user: "jane"
git_protocol: https
github.enterprise.com:
oauth_token: "gho_yyyy" # preserved from existing file
JSON¶
Sync database credentials into a JSON config file:
rules:
- name: db-config
vault_key: "db"
target:
path: "~/.myapp/config.json"
format: json
template: |
{
"database": {
"host": "{{ .host | default "localhost" }}",
"port": {{ .port | default "5432" }},
"username": "{{ .username }}",
"password": "{{ .password }}"
}
}
Vault secret:
Result (merged with existing file):
{
"database": {
"host": "db.internal",
"port": 5432,
"username": "jane",
"password": "s3cret"
},
"logging": {
"level": "info"
}
}
The logging section from the existing file is preserved.
TOML¶
Sync credentials into a TOML configuration:
rules:
- name: cargo-registry
vault_key: "cargo"
target:
path: "~/.cargo/credentials.toml"
format: toml
template: |
[registries.my-registry]
token = "{{ .token }}"
Vault secret:
Result:
[registries.my-registry]
token = "cargo_xxxxxxxxxxxx"
[registries.crates-io]
token = "existing_token" # preserved from existing file
INI¶
Sync AWS credentials into ~/.aws/credentials:
rules:
- name: aws-creds
vault_key: "aws"
target:
path: "~/.aws/credentials"
format: ini
template: |
[default]
aws_access_key_id = {{ .access_key }}
aws_secret_access_key = {{ .secret_key }}
Vault secret:
Result (merged with existing file):
[default]
aws_access_key_id = AKIAXXXXXXXX
aws_secret_access_key = wJalrXxxxxxxxx
[profile staging]
aws_access_key_id = AKIA_OTHER # preserved from existing file
aws_secret_access_key = other_key
Netrc¶
Sync machine credentials into ~/.netrc:
rules:
- name: netrc
vault_key: "netrc"
target:
path: "~/.netrc"
format: netrc
template: |
machine github.com
login {{ .user }}
password {{ .oauth_token }}
Vault secret:
Result (merged with existing file):
Entries are merged by machine name. The existing gitlab.com entry is preserved.
Text (plain)¶
Sync a private key or certificate:
rules:
- name: ssh-key
vault_key: "ssh"
target:
path: "~/.ssh/id_ed25519"
format: text
template: "{{ .private_key }}"
Vault secret:
Text format uses full replacement — the entire file content is overwritten. This is appropriate for opaque blobs like private keys and certificates where merging is not meaningful.
ssh_config¶
Surgically manage directives in ~/.ssh/config. The motivating case is exposing the local dotvault (Vault) endpoint on a remote host through a stable, per-user unix socket — one half of a dotvault-to-dotvault information-sharing setup. A RemoteForward creates the remote socket and forwards it back to 127.0.0.1:8200 on the originating machine:
rules:
- name: dotvault-forward
target:
path: "~/.ssh/config"
format: ssh_config
template: |
Host *
User {{ username }}
RemoteForward /home/{{ username }}/.ssh/dotvault.sock 127.0.0.1:8200
This rule has no vault_key — an ssh_config has no Vault-backed secrets, so the rule is keyless: dotvault never contacts Vault for it and renders the template with an empty data context. The path-building username comes from the username function (the OS account dotvault runs as), which resolves regardless because it is a template function, not a context field. dotvault matches the Host * section by its criteria line and updates only the User and RemoteForward directives inside it. Every other section, comment, and directive in the file is preserved verbatim. Because {{ username }} is stable across syncs, the RemoteForward listen path stays constant and the directive updates in place instead of accumulating duplicates. (A rule may still set vault_key if its template needs secret fields — vault_key is simply optional. See Sync rules → keyless rules.)
Keep a forward's listen path stable. A
RemoteForwardis identified for merge by its listen spec (the first argument,/home/{{ username }}/.ssh/dotvault.sockhere). If that path ever renders differently from the line already in the file, dotvault sees a new forward, appends it, and leaves the old one behind — so a forward whose listen path you change is added rather than rewritten, and you remove the stale line by hand once. This is why the path uses the stable{{ username }}function: a template that previously rendered the path with a different (or empty) value will not match and will orphan. See Sync rules → ssh_config for the full discriminator semantics.
The ssh_config format is template-only — there is no raw-data fallback, so the template field is required (see below).
Templates without the template field¶
If no template is specified, dotvault passes the raw Vault KV data map to the format handler. For YAML and JSON, this means all fields from the Vault secret are written to the file:
If the Vault secret at kv/data/users/jane/myapp contains {"api_key": "xxx", "db_pass": "yyy"}, the resulting file would have both fields merged into it.
The ssh_config format is the exception: it has no raw-data path (there is no sensible mapping from arbitrary key/value pairs to ssh directives), so a rule using it must always supply a template. Omitting it produces a clear error at sync time.
Tips¶
- Use
defaultto provide fallback values for optional fields - Use
base64encodefor credentials that need to be base64-encoded in the target format (e.g. Kubernetes secrets) - Use
quotewhen embedding values in shell scripts or contexts where quoting matters - Use
envsparingly — it reads from the dotvault process environment, not the user's shell