Env backup and secrets
Runbook
Your production secrets live in compose/.env on the VPS — not in git, not in
Postgres backups. This runbook walks through exporting that file to a password
manager secure note so you never lose JWT keys, DB passwords, or OAuth client
secrets.
compose/.env
secrets file
Not in PG dump
backup gap
1Password / Bitwarden
recommended store
Where env lives
Section titled “Where env lives”After Provisioning with OpenTofu, cloud-init renders secrets to:
~/infra/compose/compose/.envPermissions are 0600 (owner read/write only). The same path applies whether you provisioned with Tofu or cloned the monorepo manually on Hetzner (or any VPS).
What is in this file: JWT_SECRET, POSTGRES_PASSWORD, VALKEY_PASSWORD, OAuth client secrets, Stripe keys, Cloudflare tokens, WUD_GHCR_TOKEN, email provider keys, and every other runtime secret referenced by compose/.env.example.
What Postgres backups do not cover
Section titled “What Postgres backups do not cover”Nightly Postgres dumps (see Backups) capture database rows only. They do not include:
compose/.envitself- TLS private keys on disk (if stored outside the env file)
- GHCR PATs or Cloudflare API tokens you rotate separately
If you restore Postgres without a copy of .env, the database comes back but the app cannot authenticate users or send email until you reconstruct secrets.
Why not commit .env to a private git repo?
Some teams do. BoringStack defaults to password-manager backup because it avoids accidental pushes, works for non-git operators, and pairs well with team handoff. Either approach is fine — pick one and document it.
Can I use Doppler or Vault instead?
Yes. See Secrets management for the broader pattern. This runbook focuses on password-manager secure notes as the lowest-friction default.
Backup workflow
Section titled “Backup workflow”-
SSH to the VPS and confirm the env file exists:
Verify env fileon the production host $ ls -la../../infra/compose/compose/.env -
Copy contents to clipboard (never paste into public channels):
View env (local only)pipe to clipboard on your machine after scp, or use ssh + cat $ ssh root@your-vps 'cat../../infra/compose/compose/.env' -
Create a secure note in your password manager:
- Title:
BoringStack prod — example.com compose/.env - Fields: paste full file; add
Last rotated:date field - Tags:
boringstack,production,hetzner(or your host)
Recommended managers: 1Password, Bitwarden, Proton Pass. Use Secure Note type, not a login item.
- Title:
-
Verify restore drill (quarterly): paste the note into a scratch
compose/.envon a staging VPS and runSTACK=prod ./scripts/compose-up.sh config— should render without missing-variable errors.
Rotation cadence
Section titled “Rotation cadence”| Secret | Suggested rotation | Notes |
|---|---|---|
JWT_SECRET | On compromise only | Rotating invalidates all sessions |
POSTGRES_PASSWORD | Annual | Requires coordinated compose + DB update |
| OAuth client secrets | When provider prompts | Update provider console + .env |
WUD_GHCR_TOKEN | 90 days | PAT with read:packages scope |
| Email API tokens | Per provider policy | Cloudflare / Resend / SendGrid dashboards |
After every rotation: update compose/.env on the VPS, update the password-manager note, restart affected services.
Team handoff
Section titled “Team handoff”When adding an operator:
- Share the secure note via password-manager vault (not Slack/email).
- Grant SSH access separately (see Firewall & TLS).
- Point them to OAuth provider setup for any manual credential steps.
When offboarding: rotate every secret the departing operator had access to.