Skip to content
BoringStack
GitHub

Env backup and secrets

4 min read

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

After Provisioning with OpenTofu, cloud-init renders secrets to:

~/infra/compose/compose/.env

Permissions 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.

Nightly Postgres dumps (see Backups) capture database rows only. They do not include:

  • compose/.env itself
  • 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.

  1. SSH to the VPS and confirm the env file exists:

    Verify env file
    $ ls -la../../infra/compose/compose/.env
  2. Copy contents to clipboard (never paste into public channels):

    View env (local only)
    $ ssh root@your-vps 'cat../../infra/compose/compose/.env'
  3. 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.

  4. Verify restore drill (quarterly): paste the note into a scratch compose/.env on a staging VPS and run STACK=prod ./scripts/compose-up.sh config — should render without missing-variable errors.

SecretSuggested rotationNotes
JWT_SECRETOn compromise onlyRotating invalidates all sessions
POSTGRES_PASSWORDAnnualRequires coordinated compose + DB update
OAuth client secretsWhen provider promptsUpdate provider console + .env
WUD_GHCR_TOKEN90 daysPAT with read:packages scope
Email API tokensPer provider policyCloudflare / Resend / SendGrid dashboards

After every rotation: update compose/.env on the VPS, update the password-manager note, restart affected services.

When adding an operator:

  1. Share the secure note via password-manager vault (not Slack/email).
  2. Grant SSH access separately (see Firewall & TLS).
  3. Point them to OAuth provider setup for any manual credential steps.

When offboarding: rotate every secret the departing operator had access to.