HashiCorp Vault — Agent Templating

2 minute read

Vault Agent’s templating feature renders secrets into configuration files using Go’s text/template syntax (via Consul Template). Applications read plain files instead of calling the Vault API.


How Templating Works

1. Agent authenticates to Vault (auto-auth)
2. Agent reads template files with secret references
3. Agent fetches secrets from Vault
4. Agent renders secrets into destination files
5. Agent optionally runs a command (e.g., reload app)
6. Agent watches for secret changes and re-renders

Basic Template Configuration

# vault-agent.hcl

vault {
  address = "http://127.0.0.1:8200"
}

auto_auth {
  method "approle" {
    config = {
      role_id_file_path   = "/etc/vault/role-id"
      secret_id_file_path = "/etc/vault/secret-id"
    }
  }
}

template {
  source      = "/etc/vault/templates/app.env.tpl"
  destination = "/etc/app/app.env"
  perms       = 0640
  command     = "systemctl reload myapp"
}

Template File

# /etc/vault/templates/app.env.tpl
{{ with secret "secret/data/myapp/config" }}
DB_HOST={{ .Data.data.db_host }}
DB_PORT={{ .Data.data.db_port }}
DB_USER={{ .Data.data.db_user }}
DB_PASS={{ .Data.data.db_pass }}
{{ end }}

Rendered output (/etc/app/app.env):

DB_HOST=db.example.com
DB_PORT=5432
DB_USER=myapp
DB_PASS=super-secret-password

Template Syntax

Read KV v2 Secrets

{{ with secret "secret/data/myapp/config" }}
USERNAME={{ .Data.data.username }}
PASSWORD={{ .Data.data.password }}
{{ end }}

Note: KV v2 uses secret/data/PATH and .Data.data.KEY (double data).

Read KV v1 Secrets

{{ with secret "kv/myapp/config" }}
USERNAME={{ .Data.username }}
PASSWORD={{ .Data.password }}
{{ end }}

Read Dynamic Secrets

{{ with secret "database/creds/my-role" }}
DB_USER={{ .Data.username }}
DB_PASS={{ .Data.password }}
{{ end }}

Read PKI Certificates

{{ with secret "pki_int/issue/web-server" "common_name=api.example.com" "ttl=24h" }}
{{ .Data.certificate }}
{{ .Data.issuing_ca }}
{{ end }}

Conditional Logic

{{ with secret "secret/data/myapp/config" }}
{{ if .Data.data.debug_enabled }}
DEBUG=true
LOG_LEVEL=debug
{{ else }}
DEBUG=false
LOG_LEVEL=info
{{ end }}
{{ end }}

Loops

{{ range secrets "secret/metadata/myapp/" }}
# Secret: {{ . }}
{{ with secret (printf "secret/data/myapp/%s" .) }}
{{ range $key, $value := .Data.data }}
{{ $key }}={{ $value }}
{{ end }}
{{ end }}
{{ end }}

Multiple Templates

# Render database config
template {
  source      = "/etc/vault/templates/db.conf.tpl"
  destination = "/etc/myapp/db.conf"
  command     = "systemctl reload myapp"
}

# Render TLS certificates
template {
  source      = "/etc/vault/templates/cert.tpl"
  destination = "/etc/myapp/tls/cert.pem"
  command     = "systemctl reload nginx"
}

template {
  source      = "/etc/vault/templates/key.tpl"
  destination = "/etc/myapp/tls/key.pem"
  command     = "systemctl reload nginx"
}

# Render as JSON
template {
  source      = "/etc/vault/templates/config.json.tpl"
  destination = "/etc/myapp/config.json"
}

JSON Config Template

# /etc/vault/templates/config.json.tpl
{{ with secret "secret/data/myapp/config" }}
{
  "database": {
    "host": "{{ .Data.data.db_host }}",
    "port": {{ .Data.data.db_port }},
    "username": "{{ .Data.data.db_user }}",
    "password": "{{ .Data.data.db_pass }}"
  },
  "redis": {
    "url": "{{ .Data.data.redis_url }}"
  }
}
{{ end }}

Inline Templates

For short templates, use inline content instead of a file:

template {
  contents    = "{{ with secret \"secret/data/myapp/config\" }}DB_PASS={{ .Data.data.db_pass }}{{ end }}"
  destination = "/etc/myapp/db-password"
  perms       = 0600
}

Template Options

Option Description
source Path to the template file
destination Path to write the rendered output
contents Inline template (alternative to source)
command Command to run after rendering
perms File permissions for destination
error_on_missing_key Fail if a template key is missing
exec Execute a child process with the rendered environment

Quick Reference

Pattern Template Syntax
KV v2 read {{ with secret "secret/data/PATH" }}{{ .Data.data.KEY }}{{ end }}
KV v1 read {{ with secret "kv/PATH" }}{{ .Data.KEY }}{{ end }}
Dynamic secret {{ with secret "database/creds/ROLE" }}{{ .Data.username }}{{ end }}
PKI cert {{ with secret "pki_int/issue/ROLE" "common_name=CN" }}{{ .Data.certificate }}{{ end }}
List secrets {{ range secrets "secret/metadata/PATH/" }}{{ . }}{{ end }}

Next: Sidecar Pattern