Vault Bootstrap Runbook [Synology NAS, Portainer stack]¶
Scope: minimal, temporary Vault with Raft storage and TLS. Single node. Non-root uid,gid.
Audience: SRE on NAS01.
Hostname: vault
and vault.SpecterRealm.com
.
TL,DR¶
- Confirm container is up, hostname is
vault
, web UI loads - Set env in the Vault container:
VAULT_ADDR
,VAULT_CACERT
,VAULT_TOKEN
- If first run, init then unseal with 3 keys
- Run
scripts/vault/init.sh
bootstrap (or/tmp/init.sh
inside container if testing), or execute equivalent manual steps below - Store unseal shares and root token in M365 Secure Notes
- Enable audit to file, seed initial secrets, take a Raft snapshot
- Add the backup paths to Hyper Backup
Constants¶
- Vault URL:
https://vault.SpecterRealm.com:8200
- In-container URL:
https://vault:8200
- CA and certs in container:
/pki/ca.pem
,/pki/vault.crt
,/pki/vault.key
- Data:
/volume1/docker/appdata/vault/data
- Audit:
/volume1/docker/appdata/vault/audit
- Config:
/volume1/docker/appdata/vault/config/vault.hcl
- Run as
user: "1030:100"
,SKIP_SETCAP=true
,disable_mlock=true
Prerequisites¶
- Portainer stack deployed with volumes bound to the paths above.
- TLS files exist and are mounted as files, not a directory.
- Container hostname set to
vault
in the stack. - The CA that signed
vault.crt
is present at/pki/ca.pem
in the container.
Sanity:
# inside the vault container shell
export VAULT_ADDR=https://vault:8200
export VAULT_CACERT=/pki/ca.pem
vault status || true
Initialize and Unseal¶
Initialize on first ever run:
vault operator init -key-shares=5 -key-threshold=3
# Save: 5 unseal keys, root token. Store in M365.
Unseal:
vault operator unseal # run 3 times with different keys
vault status # should show Sealed=false, Mode=active
Lost root token? Generate a new one:
vault operator generate-root -generate-otp
vault operator generate-root -init -otp=<otp> # copy Nonce
vault operator generate-root -nonce=<nonce> # repeat 3 times, paste different unseal keys
vault operator generate-root -decode=<encoded_token> -otp=<otp> # new root token
One-shot Bootstrap Script¶
Create and run the script inside the vault container.
cat > /tmp/init.sh <<'EOF'
#!/usr/bin/env sh
set -euo pipefail
VAULT_ADDR="${VAULT_ADDR:-https://vault:8200}"
VAULT_CACERT="${VAULT_CACERT:-/pki/ca.pem}"
ROOT_TOKEN="${VAULT_TOKEN:-}"
CF_API_TOKEN="${CF_API_TOKEN:-}" # optional seed
PG_USER="${PG_USER:-svc_app}"
PG_PASS="${PG_PASS:-}" # optional seed
PG_URL="${PG_URL:-postgres://db:5432/app}"
if [ -z "${ROOT_TOKEN}" ]; then
echo "ERROR: VAULT_TOKEN (root) not set" >&2
exit 1
fi
export VAULT_ADDR VAULT_CACERT VAULT_TOKEN="${ROOT_TOKEN}"
vault status || true
# Enable KV v2
vault secrets list -format=json | grep -q '"kv/":' || \
vault secrets enable -path=kv kv-v2
# Policies
cat >/tmp/admin.hcl <<'POLICY'
path "sys/*" { capabilities = ["create","read","update","delete","list","sudo"] }
path "auth/*" { capabilities = ["create","read","update","delete","list","sudo"] }
path "kv/*" { capabilities = ["create","read","update","delete","list"] }
POLICY
vault policy write admin /tmp/admin.hcl
cat >/tmp/service.hcl <<'POLICY'
path "kv/data/*" { capabilities = ["read"] }
path "kv/metadata/*" { capabilities = ["list"] }
POLICY
vault policy write service /tmp/service.hcl
# Auth
vault auth list -format=json | grep -q '"userpass/":' || \
vault auth enable userpass
ADMIN_PW="${ADMIN_PW:-$(head -c 18 /dev/urandom | base64)}"
vault write auth/userpass/users/admin password="${ADMIN_PW}" policies=admin
echo "BOOTSTRAP_ADMIN_USER=admin"
echo "BOOTSTRAP_ADMIN_PASS=${ADMIN_PW}"
if [ -n "${PG_PASS}" ] || [ -n "${CF_API_TOKEN}" ]; then
SVC_PW="${SVC_PW:-$(head -c 18 /dev/urandom | base64)}"
vault write auth/userpass/users/svc password="${SVC_PW}" policies=service
echo "SERVICE_USER=svc"
echo "SERVICE_PASS=${SVC_PW}"
fi
# Audit
mkdir -p /vault/audit
vault audit list -format=json | grep -q '"file/":' || \
vault audit enable file file_path=/vault/audit/audit.log
# Seed secrets
[ -n "${CF_API_TOKEN}" ] && vault kv put kv/cloudflare/api token="${CF_API_TOKEN}" && echo "Seeded: kv/cloudflare/api"
[ -n "${PG_PASS}" ] && vault kv put kv/db/postgres username="${PG_USER}" password="${PG_PASS}" url="${PG_URL}" && echo "Seeded: kv/db/postgres"
# Smoke and snapshot
vault kv put kv/test hello=world >/dev/null
vault kv get kv/test >/dev/null && echo "KV test OK"
SNAP=/vault/data/bootstrap-$(date +%Y%m%d-%H%M%S).snap
vault operator raft snapshot save "${SNAP}"
echo "Snapshot: ${SNAP}"
EOF
chmod +x /tmp/init.sh
# Required: export VAULT_TOKEN to your current root token
VAULT_TOKEN=<root-token-here> /tmp/init.sh
Optional seeds:
export CF_API_TOKEN=<CF token> # optional
export PG_PASS=<postgres password> # optional
export PG_USER=svc_app
export PG_URL="postgres://host:5432/app"
VAULT_TOKEN=<root-token> /tmp/init.sh
Manual Minimal Steps (if not using script)¶
export VAULT_ADDR=https://vault:8200
export VAULT_CACERT=/pki/ca.pem
export VAULT_TOKEN=<root>
vault secrets enable -path=kv kv-v2
cat >/tmp/admin.hcl <<'EOF'
path "sys/*" { capabilities = ["create","read","update","delete","list","sudo"] }
path "auth/*" { capabilities = ["create","read","update","delete","list","sudo"] }
path "kv/*" { capabilities = ["create","read","update","delete","list"] }
EOF
vault policy write admin /tmp/admin.hcl
vault auth enable userpass
ADMIN_PW="$(head -c 18 /dev/urandom | base64)"; echo "admin: ${ADMIN_PW}"
vault write auth/userpass/users/admin password="${ADMIN_PW}" policies=admin
mkdir -p /vault/audit
vault audit enable file file_path=/vault/audit/audit.log
Backups¶
Include in Hyper Backup:
- /volume1/docker/appdata/vault/data
[Raft, snapshots]
- /volume1/docker/appdata/vault/audit
- /volume1/docker/appdata/vault/config
- /volume1/docker/appdata/pki
[include ca.key
if you want to reissue]
Run a snapshot after bootstrap:
vault operator raft snapshot save /vault/data/post-bootstrap.snap
What to save¶
- Unseal key shares: all 5. Threshold 3.
- Current root token.
- If used: bootstrap
admin
userpass andsvc
userpass credentials. - Snapshot file names and locations.
Troubleshooting quick hits¶
-
permission denied /vault/data/vault.db
Fix host ownership to 1030:100, ensure parents 755, data 700. Remove stalevault.db
,raft/
. Clear DSM ACLs if present. -
mlock failures
Setdisable_mlock = true
invault.hcl
. For ulimit route, addulimits: { memlock: -1 }
and keepIPC_LOCK
. -
TLS cert not found or permission denied
Bind files individually:Ensure- /volume1/.../pki/vault.crt:/pki/vault.crt:ro - /volume1/.../pki/vault.key:/pki/vault.key:ro - /volume1/.../pki/ca.pem:/pki/ca.pem:ro
/pki/vault.key
is readable by uid 1030. -
x509 cannot validate 127.0.0.1
Usehttps://vault:8200
inside the container or add IP SANs when minting the cert. -
CAP_SETFCAP errors when running as 1030
SetSKIP_SETCAP=true
in environment. -
403 permission denied on CLI
VAULT_TOKEN
not set or wrong. Re-export it or generate a new root withgenerate-root
.
Restore Runbook¶
- Restore backup folders to the same paths.
- Redeploy Portainer stack.
- Unseal with any 3 shares.
export VAULT_ADDR=https://vault:8200 VAULT_CACERT=/pki/ca.pem
vault status
should show active.- Re-enable audit if the path changed.
- Rotate root token. Optionally disable
userpass
when OIDC is ready.
Quick Overview
What: Shared abbreviations and hover tooltips for the campaign. Why: Ensures acronyms and jargon stay consistent across all docs. Time: Ongoing; update whenever a new term is introduced. XP: +5 per new acronym (like Whose Line, the points are made up but the fun is real).
Think of this file as your universal translator—every acronym here powers hover definitions site-wide.
🎉 Abbreviations updated! You’ve earned +5 XP for each new term (points don’t matter, but consistency does).