# Vaultwarden — VPS (ROOTLESS PODMAN) port of the home server's ~/Server/vaultwarden, # staged 2026-08-12. # # ⚠️ STAGED, NOT AUTHORITATIVE. The home instance is still live and still serving # vault.gbrown.org. This copy holds a point-in-time snapshot of the SQLite database. # DO NOT log into this one and add credentials before cutover -- the two would diverge # and one side's changes would be lost. # # Cutover procedure (do it in this order, it matters): # 1. stop vaultwarden on the home server (clean SQLite, no torn WAL) # 2. rsync -a server.gib:~/Server/vaultwarden/volume/ ~/Server/Vaultwarden/volume/ # 3. systemctl --user restart podman-vaultwarden # 4. point vault.gbrown.org at the VPS, add the NPM proxy host # 5. verify a login + an item decrypts, THEN leave home stopped # # The SQLite copy taken while home was running is crash-consistent only. Step 1 is what # makes it clean -- don't skip it. # # Rootless adaptations: # - :Z on the data mount (SELinux Enforcing). # - /etc/localtime mount dropped in favour of TZ (AGENTS.md §8). # - No published port; NPM proxies to http://vaultwarden:80 over nginx-bridge. # - Image starts as root and drops privileges internally, so no userns_mode needed. # # NOTE: ADMIN_TOKEN is inline here, matching the home server's file verbatim. It grants # access to /admin. Worth moving to a mode-600 .env at some point -- compose files get # rsynced offsite by ~/Server/backup. networks: nginx-bridge: external: true services: vaultwarden: image: docker.io/vaultwarden/server:latest container_name: vaultwarden hostname: vaultwarden domainname: vault.gbrown.org networks: - nginx-bridge environment: - TZ=America/New_York - DOMAIN=https://vault.gbrown.org - LOGIN_RATELIMIT_MAX_BURST=10 - LOGIN_RATELIMIT_SECONDS=60 - WEB_VAULT_ENABLED=true - SIGNUPS_ALLOWED=false - WEBSOCKET_ENABLED=true - ADMIN_TOKEN=${ADMIN_TOKEN} labels: com.centurylinklabs.watchtower.enable: "true" volumes: - ./volume:/data:Z tty: true restart: unless-stopped