8  Secrets Management

Note

Bitwarden as the source of truth, podman secret as the runtime interface, Vaultwarden as a self-hosted backup.

8.1 The secret lifecycle

  • Secrets are created and stored in Bitwarden (cloud-hosted vault)
  • At deploy time, pyinfra retrieves secrets via the Bitwarden CLI and injects them as podman secrets
  • At runtime, containers read secrets from files mounted by podman (not environment variables)
  • Secrets never appear in quadlet files, environment blocks, or the Git repo

8.2 Bitwarden item conventions

  • One Bitwarden item per service, named to match the service name in pyinfra/services.py
  • Custom fields map to individual secrets (e.g., AUTHENTIK_SECRET_KEY, PG_PASS)
  • pyinfra looks up items by name, extracts fields, creates podman secret entries for the service user

8.3 podman secret integration

  • Quadlet .container files reference secrets by name: Secret=secret_name,type=mount
  • Podman mounts the secret as a file inside the container (default: /run/secrets/{name})
  • Service users each have their own secret namespace; secrets are not shared across users

8.4 Vaultwarden as break-glass

  • Vaultwarden runs on the host as a self-hosted Bitwarden-compatible server
  • If Bitwarden cloud is unreachable, Vaultwarden has a local copy of all credentials
  • Family members use Vaultwarden for their own password management; the maintainer uses it as a fallback for infrastructure secrets

8.5 Why not Vault, SOPS, or dotenv

  • HashiCorp Vault: powerful but a full distributed system to maintain; overkill for a single host
  • SOPS: good for encrypting files in Git, but secrets still need to reach podman secret at runtime — adds a layer without removing one
  • .env files: secrets in plaintext on disk, no audit trail, easy to accidentally commit
  • podman secret with Bitwarden as the source of truth is the simplest path that keeps secrets out of the repo and off disk in plaintext