kitestacks-homelab/docs/vault-setup.md
kenpat dbcf51993d ops: add HashiCorp Vault for secrets management
Replaces .env files across all KiteStacks apps. Vault runs as a Docker
container bound to 127.0.0.1:8200 with file storage backend.

- apps/vault/: compose file + vault.hcl config (TLS disabled, localhost only)
- scripts/vault-env.sh: fetches secret from Vault KV and injects as env
  vars before running docker compose (drops the .env pattern entirely)
- scripts/vault-init.sh: one-time init — GPG-encrypts unseal keys to
  ~/.vault-keys.gpg, creates kitestacks policy + limited app token
- scripts/vault-unseal.sh: post-restart unseal via GPG-decrypted key
- docs/vault-setup.md: full setup guide including secret migration steps

Usage: vault-env.sh kitestacks/authentik -- docker compose up -d

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-19 03:01:12 -05:00

3.4 KiB

HashiCorp Vault: Secrets Management

Vault replaces .env files across all KiteStacks apps. Secrets live in Vault's encrypted storage; nothing sensitive is ever written to disk in plaintext or committed to git.

Architecture

monk (T14s)
└── Docker: hashicorp/vault:1.17
    ├── Bound to 127.0.0.1:8200 only (never public)
    ├── Storage: vault_data Docker volume (encrypted at rest)
    └── secret/kitestacks/<app>
        ├── kitestacks/authentik   → AUTHENTIK_SECRET_KEY, PG_PASS
        ├── kitestacks/cloudflared → TUNNEL_TOKEN
        ├── kitestacks/kite-ai     → WEBUI_SECRET_KEY, ...
        └── kitestacks/openproject → OPENPROJECT_OIDC_SECRET

scripts/vault-env.sh pulls secrets at deploy time and injects them as env vars.
No .env files. No secrets in git.

One-time setup

1. Install Vault CLI on monk

curl -fsSL https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
sudo apt update && sudo apt install -y vault

2. Start Vault

cd ~/kitestacks-homelab/apps/vault
docker compose up -d

3. Initialize and unseal

export VAULT_ADDR=http://127.0.0.1:8200
# Needs GPG key set up: gpg --gen-key if you don't have one
GPG_RECIPIENT=kenpat7177@gmail.com bash ~/kitestacks-homelab/scripts/vault-init.sh

This saves encrypted keys to ~/.vault-keys.gpg and creates a kitestacks policy token.

4. Write your first secrets

export VAULT_ADDR=http://127.0.0.1:8200
# Use the app token created by vault-init.sh
# (or root token from: gpg --decrypt ~/.vault-keys.gpg | python3 -c "import json,sys; print(json.load(sys.stdin)['root_token'])")

vault kv put secret/kitestacks/authentik \
  AUTHENTIK_SECRET_KEY="$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | head -c 64)" \
  PG_PASS="your-postgres-password"

vault kv put secret/kitestacks/cloudflared \
  TUNNEL_TOKEN="your-cf-tunnel-token"

vault kv put secret/kitestacks/openproject \
  OPENPROJECT_OIDC_SECRET="your-oidc-secret"

5. Write app token to ~/.vault-token

# The app token was printed by vault-init.sh — paste it here:
echo "hvs.YOURTOKEN" > ~/.vault-token
chmod 600 ~/.vault-token

Deploying apps with Vault

Instead of docker compose up -d, use vault-env.sh:

# Add scripts/ to PATH or use full path
PATH="$HOME/kitestacks-homelab/scripts:$PATH"

vault-env.sh kitestacks/authentik -- \
  docker compose -f apps/authentik/docker-compose.yml up -d

vault-env.sh kitestacks/cloudflared -- \
  docker compose -f apps/cloudflared/docker-compose.yml up -d

After a reboot (unseal)

Vault is sealed after every restart. Unseal it before any deployments:

export VAULT_ADDR=http://127.0.0.1:8200
bash ~/kitestacks-homelab/scripts/vault-unseal.sh

Or add to a post-boot script / systemd unit that runs after docker.

Verifying secrets

vault kv get secret/kitestacks/authentik
vault kv list secret/kitestacks/

Why not just use Docker secrets?

Docker Swarm secrets are great but require Swarm mode. For a single-node compose setup, Vault gives you:

  • Central secret versioning and audit log
  • Rotation without touching compose files
  • Same pattern you'd use in a real Kubernetes/Nomad environment