HashiCorp Vault is integrated as an optional secrets management layer for the PostgreSQL HA Terraform + Docker stack. All Vault resources are guarded by feature flags (vault_enabled, vault_agent_enabled) so the stack runs without Vault by default.
| Property | Value |
|---|---|
| Image | hashicorp/vault:1.17.3 |
| Port | 8200 (configurable via vault_port) |
| Storage backend | Raft (built-in, no external DB or Redis required) |
| Auth method | AppRole |
| Secrets engine | KV v2 at secret/ |
| Feature flag | vault_enabled = true in ha-test.tfvars |
| Sidecar flag | vault_agent_enabled = true in ha-test.tfvars |
secret/data/pg/postgres → postgres_user, postgres_password
secret/data/pg/replication → replication_password
secret/data/pgbouncer → pgbouncer_user, pgbouncer_password
Seeded by vault-bootstrap.sh using Terraform-generated passwords.
sequenceDiagram
participant TF as Terraform apply
participant BS as vault-bootstrap.sh
participant V as Vault
participant C as Container (pg-node / pgbouncer)
TF->>BS: null_resource.vault_init executes bootstrap
BS->>V: Init + unseal (dev)
BS->>V: Enable KV v2, create pg-role policy
BS->>V: Enable AppRole, generate role_id + secret_id
BS->>V: Seed secrets (pg/postgres, pg/replication)
BS-->>TF: Write .vault-bootstrap/approle_pg-role.json
BS-->>TF: Write .vault-bootstrap/role_id + secret_id (split)
C->>V: POST /v1/auth/approle/login {role_id, secret_id}
V-->>C: Vault token
C->>V: GET /v1/secret/data/pg/postgres
V-->>C: {postgres_user, postgres_password}
- Bootstrap creates policy
pg-rolegranting read/list onsecret/data/pg/*andsecret/data/pgbouncer/*. - AppRole is enabled; role_id + secret_id are written to
.vault-bootstrap/(dev only, gitignored). - Terraform mounts
approle_pg-role.jsoninto containers at/etc/vault/approle_pg-role.json. - Entrypoints call
vault-secrets.shto login via AppRole and fetch KV data.
When vault_agent_enabled = true, a vault-agent container runs as a sidecar:
- Authenticates with AppRole from
.vault-bootstrap/role_id+.vault-bootstrap/secret_id - Renders
vault/agent/templates/postgres.hcl→/etc/vault/secrets/postgres.env - Shares the
vault-agent-secretsDocker volume with pg-node and pgbouncer containers (read-only)
See VAULT-AGENT-SIDECAR.md for the full sidecar design.
| File | Purpose |
|---|---|
main-vault-init.tf |
Vault container, volume, network attachment; null_resource.vault_init bootstrap |
main-vault-agent.tf |
Vault Agent container, vault-agent-secrets volume, permission fix |
variables-ha.tf |
vault_enabled, vault_agent_enabled, vault_port flags |
vault-bootstrap.sh |
Initializes Vault, creates policy/AppRole, seeds KV, calls split script |
vault-bootstrap-split.sh |
Splits approle_<role>.json → plain-text role_id + secret_id files |
vault-secrets.sh |
Library: fetch_secret_from_vault(), create_secret_in_vault() |
vault/agent/agent.hcl |
Vault Agent config: AppRole auth, token sink, template destination |
vault/agent/templates/postgres.hcl |
Consul-template file rendering postgres.env from KV v2 |
vault-bootstrap.sh runs inside the Vault container during terraform apply:
- Initializes Vault (single unseal key, dev convenience)
- Uses root token from
vault_root_tokeninha-test.tfvars(default:dev-root-token) - Enables KV v2 at
secret/ - Creates
pg-rolepolicy - Enables AppRole; generates role_id + secret_id
- Seeds secrets with Terraform-generated passwords
- Calls
vault-bootstrap-split.shto write plain-textrole_id/secret_id
.vault-bootstrap/is gitignored — contains live credentials, never commit- In production, use Vault Agent Injector (K8s) or this sidecar pattern
- Mark Terraform outputs with
sensitive = truefor any credential outputs - Rotate secret_ids regularly; set a short
secret_id_ttlin the AppRole role config
# Vault reachability
curl -s http://localhost:8200/v1/sys/health | python3 -m json.tool
# Check AppRole bootstrap files
ls -la .vault-bootstrap/
# Regenerate split files
bash vault-bootstrap-split.sh pg-roleSee VAULT-TROUBLESHOOTING.md for full diagnostics.
Further reading: