HashiCorp Vault Setup
Secure secret management for PiSovereign using HashiCorp Vault
Vault is included in the Docker Compose stack and initialized automatically on first run. This guide covers how secrets are structured, how to store them, and how Vault integrates with PiSovereign.
Overview
HashiCorp Vault provides centralized secret management with encryption at rest and in transit, fine-grained access control, audit logging, and secret rotation. PiSovereign’s Docker Compose setup includes Vault with automatic initialization via the vault-init sidecar container.
How It Works
┌─────────────────────────────────────────────────────┐
│ PiSovereign │
│ ┌─────────────────────────────────────────────┐ │
│ │ ChainedSecretStore │ │
│ │ ┌─────────────┐ ┌──────────────────┐ │ │
│ │ │ VaultSecret │ → │ EnvironmentSecret │ │ │
│ │ │ Store │ │ Store │ │ │
│ │ └─────────────┘ └──────────────────┘ │ │
│ └─────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────┐
│ HashiCorp Vault │
│ ┌──────────────┐ ┌─────────────┐ ┌───────────┐ │
│ │ KV v2 Engine │ │ AppRole │ │ Audit │ │
│ │ │ │ Auth │ │ Log │ │
│ └──────────────┘ └─────────────┘ └───────────┘ │
└─────────────────────────────────────────────────────┘
PiSovereign uses a ChainedSecretStore that tries multiple backends in order:
- Vault (primary) — Production secrets stored securely
- Environment variables (fallback) — Overrides for development or CI
Initialization
Vault is initialized on first deployment via the Docker Compose init container. Run manually if needed:
cd docker
docker compose exec vault /vault/init.sh
Important: Save the unseal key and root token printed to stdout. Loss of the unseal key means loss of access to secrets.
After a container restart, Vault may need to be unsealed:
docker compose exec vault vault operator unseal <UNSEAL_KEY>
Storing Secrets
Store integration credentials in Vault after initialization:
# Enter the Vault container
docker compose exec vault sh
# WhatsApp credentials
vault kv put secret/pisovereign/whatsapp \
access_token="your-meta-access-token" \
app_secret="your-app-secret"
# Email credentials (IMAP/SMTP password or Bridge password)
vault kv put secret/pisovereign/email \
password="your-email-password"
# CalDAV credentials
vault kv put secret/pisovereign/caldav \
username="your-username" \
password="your-password"
# OpenAI API key (for speech fallback)
vault kv put secret/pisovereign/openai \
api_key="sk-your-openai-key"
# Brave Search API key
vault kv put secret/pisovereign/websearch \
brave_api_key="BSA-your-key"
# Signal phone number
vault kv put secret/pisovereign/signal \
phone_number="+491701234567"
# Verify a secret
vault kv get secret/pisovereign/whatsapp
Secret Paths
PiSovereign expects secrets at these paths:
| Secret | Vault Path | Environment Variable Fallback |
|---|---|---|
| WhatsApp Access Token | secret/pisovereign/whatsapp → access_token | PISOVEREIGN_WHATSAPP_ACCESS_TOKEN |
| WhatsApp App Secret | secret/pisovereign/whatsapp → app_secret | PISOVEREIGN_WHATSAPP_APP_SECRET |
| Email Password | secret/pisovereign/email → password | PISOVEREIGN_EMAIL_PASSWORD |
| CalDAV Username | secret/pisovereign/caldav → username | PISOVEREIGN_CALDAV_USERNAME |
| CalDAV Password | secret/pisovereign/caldav → password | PISOVEREIGN_CALDAV_PASSWORD |
| OpenAI API Key | secret/pisovereign/openai → api_key | PISOVEREIGN_OPENAI_API_KEY |
| Brave Search Key | secret/pisovereign/websearch → brave_api_key | PISOVEREIGN_WEBSEARCH_BRAVE_API_KEY |
| Signal Phone Number | secret/pisovereign/signal → phone_number | PISOVEREIGN_SIGNAL__PHONE_NUMBER |
AppRole Authentication
For production, use AppRole instead of the root token. AppRole provides short-lived tokens with scoped permissions.
Create Policy
docker compose exec vault sh
vault policy write pisovereign - <<EOF
path "secret/data/pisovereign/*" {
capabilities = ["read"]
}
path "secret/metadata/pisovereign/*" {
capabilities = ["list"]
}
path "auth/token/renew-self" {
capabilities = ["update"]
}
EOF
Configure AppRole
vault auth enable approle
vault write auth/approle/role/pisovereign \
token_policies="pisovereign" \
token_ttl=1h \
token_max_ttl=4h \
secret_id_ttl=720h \
secret_id_num_uses=0
# Get Role ID
vault read auth/approle/role/pisovereign/role-id
# Generate Secret ID
vault write -f auth/approle/role/pisovereign/secret-id
Then configure PiSovereign to use AppRole in config.toml:
[vault]
address = "http://vault:8200"
role_id = "12345678-1234-1234-1234-123456789012"
secret_id = "abcd1234-abcd-1234-abcd-abcd12345678"
mount_path = "secret"
timeout_secs = 5
Tip: Store
secret_idas an environment variable rather than in the config file:export PISOVEREIGN_VAULT_SECRET_ID="abcd1234-..."
Operations
Secret Rotation
Update a secret without downtime — PiSovereign reads the latest version automatically:
vault kv put secret/pisovereign/whatsapp \
access_token="new-access-token" \
app_secret="same-app-secret"
View secret versions or rollback:
vault kv metadata get secret/pisovereign/whatsapp
vault kv rollback -version=2 secret/pisovereign/whatsapp
Backup
# Backup Vault data volume
docker run --rm -v docker_vault-data:/data -v $(pwd):/backup \
alpine tar czf /backup/vault-backup-$(date +%Y%m%d).tar.gz /data
For disaster recovery, ensure you have the unseal key and root token stored securely in a separate location.
Troubleshooting
Cannot connect to Vault
docker compose exec vault vault status
docker compose logs vault
Permission denied
# Verify the token has the correct policy
docker compose exec vault vault token lookup
docker compose exec vault vault policy read pisovereign
Secret not found
# Verify the secret exists
docker compose exec vault vault kv get secret/pisovereign/whatsapp
# Check the mount path
docker compose exec vault vault secrets list
Vault sealed after restart
docker compose exec vault vault operator unseal <UNSEAL_KEY>
Next Steps
- Configuration Reference — All PiSovereign options
- Security Hardening — Vault security best practices
- Docker Setup — Full deployment reference