Skip to content

Operations

Scope

Production deployment, seal/unseal procedures, secret engines, audit logging, and HA patterns for HashiCorp Vault.

Deployment Patterns

HA Architectures

Pattern Storage Backend Auto-Unseal Use Case
Integrated Raft Built-in Raft AWS KMS / Azure Key Vault Most deployments
Consul Backend Consul cluster Cloud KMS Legacy, transitioning away
External PostgreSQL PostgreSQL Cloud KMS Managed DB preference
# Raft storage (recommended)
storage "raft" {
  path    = "/vault/data"
  node_id = "vault-0"
  retry_join {
    leader_api_addr = "https://vault-1:8200"
  }
}

# Auto-unseal with AWS KMS
seal "awskms" {
  region     = "us-east-1"
  kms_key_id = "alias/vault-unseal"
}

Kubernetes Deployment (Helm)

helm install vault hashicorp/vault \
  --namespace vault --create-namespace \
  --set server.ha.enabled=true \
  --set server.ha.replicas=3 \
  --set server.ha.raft.enabled=true \
  --set server.auditStorage.enabled=true \
  --set injector.enabled=true

Secret Engines

Engine Use Case Path
KV v2 Static secrets secret/
PKI TLS certificates pki/
Transit Encryption as a Service transit/
Database Dynamic DB credentials database/
AWS Dynamic IAM credentials aws/
Kubernetes Service account tokens kubernetes/

Dynamic Database Secrets

# Configure database secret engine
vault write database/config/mydb \
  plugin_name=postgresql-database-plugin \
  connection_url="postgresql://{{username}}:{{password}}@db:5432/mydb" \
  allowed_roles="readonly" \
  username="vault" \
  password="vault-password"

# Create role
vault write database/roles/readonly \
  db_name=mydb \
  creation_statements="CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}'; GRANT SELECT ON ALL TABLES IN SCHEMA public TO \"{{name}}\";" \
  default_ttl="1h" \
  max_ttl="24h"

# Get dynamic credentials
vault read database/creds/readonly

Seal/Unseal Operations

# Initialize (first time only)
vault operator init -key-shares=5 -key-threshold=3

# Manual unseal (without auto-unseal)
vault operator unseal <key1>
vault operator unseal <key2>
vault operator unseal <key3>

# Check seal status
vault status

Recovery Keys

With auto-unseal enabled, the unseal keys become recovery keys. Store them securely (e.g., in separate physical safes or HSMs).

Audit Logging

# Enable file audit
vault audit enable file file_path=/vault/audit/audit.log

# Enable syslog
vault audit enable syslog tag="vault" facility="AUTH"

Monitoring

# Token creation rate
rate(vault_token_create_count[5m])

# Authentication failures
vault_core_handle_login_request{error!=""}

# Seal status
vault_core_unsealed

# Storage backend latency
vault_barrier_put{quantile="0.99"}

Common Issues

Issue Root Cause Resolution
Vault sealed after restart No auto-unseal configured Configure cloud KMS auto-unseal
Token expired TTL misconfigured Increase default_ttl or use periodic tokens
Policy denied Insufficient permissions Review policy paths and capabilities
Raft leader election failures Network partition Check node connectivity, raft peers
High memory usage Too many leases Review lease TTLs, enable revocation

Commands & Recipes

Setup & Init

# Dev mode (testing only!)
vault server -dev

# Production init
vault operator init -key-shares=5 -key-threshold=3

# Unseal (3 of 5 keys)
vault operator unseal <key1>
vault operator unseal <key2>
vault operator unseal <key3>

# Login
export VAULT_ADDR="https://vault.example.com:8200"
vault login <root-token>

KV Secrets

# Enable KV v2
vault secrets enable -path=secret kv-v2

# Write secret
vault kv put secret/myapp/db username="admin" password="s3cr3t"

# Read secret
vault kv get secret/myapp/db
vault kv get -field=password secret/myapp/db

# List secrets
vault kv list secret/myapp/

Dynamic Database Secrets

# Enable database engine
vault secrets enable database

# Configure PostgreSQL
vault write database/config/mydb \
  plugin_name=postgresql-database-plugin \
  allowed_roles="readonly" \
  connection_url="postgresql://{{username}}:{{password}}@db:5432/mydb?sslmode=disable" \
  username="vault_admin" \
  password="vault_pass"

# Create role (dynamic creds with 1h TTL)
vault write database/roles/readonly \
  db_name=mydb \
  creation_statements="CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}'; GRANT SELECT ON ALL TABLES IN SCHEMA public TO \"{{name}}\";" \
  default_ttl="1h" \
  max_ttl="24h"

# Get dynamic credentials (a new user every time!)
vault read database/creds/readonly

Kubernetes Auth

# Enable K8s auth
vault auth enable kubernetes

# Configure
vault write auth/kubernetes/config \
  kubernetes_host="https://kubernetes.default.svc"

# Create role for app
vault write auth/kubernetes/role/myapp \
  bound_service_account_names=myapp \
  bound_service_account_namespaces=default \
  policies=myapp-policy \
  ttl=1h

Transit (Encryption-as-a-Service)

# Enable Transit
vault secrets enable transit

# Create encryption key
vault write -f transit/keys/mykey

# Encrypt data
vault write transit/encrypt/mykey plaintext=$(echo -n "secret data" | base64)

# Decrypt data
vault write transit/decrypt/mykey ciphertext="vault:v1:..."

Sources