Self-hosting guide

DreyVault is one container (ghcr.io/dreyfvr/dreyvault) that serves the API and the web vault on port 3000 and keeps everything in /data. You run your own server; nobody else hosts your vault.

Requirements

  • Docker with Compose v2 (Linux, macOS, Windows/WSL2, a NAS), amd64 or arm64. 1 CPU, 512 MB RAM and a few hundred MB of disk are plenty for a household.
  • HTTPS. Browsers allow biometric sign-in (WebAuthn) and the extension's server connection only over HTTPS. The installer sets it up.

Install

curl -fsSL https://dreyvault.abhishekm.work/install.sh | sh

It asks for an HTTPS method, a domain and who may sign up, writes ~/dreyvault/.env (generated secrets) and ~/dreyvault/docker-compose.yml, starts the stack and prints the URL. Re-running it keeps your secrets and answers. Options: --dir DIR, --no-start, --yes (non-interactive; answers from DV_DOMAIN, DV_HTTPS, DV_SIGNUP, DV_TUNNEL_TOKEN, DV_TS_AUTHKEY, DV_TS_HOSTNAME, DV_TS_TAILNET, DV_ACME_EMAIL, DV_LAN_IP). It never uses sudo; if Docker is missing it tells you how to install it.

HTTPS methods

Method Use when You provide Notes
Cloudflare Tunnel Reach it from anywhere, no open ports A domain on Cloudflare, a tunnel token In the tunnel, add public hostname <domain> → http://dreyvault:3000.
Tailscale Serve Only your own devices, anywhere A tailnet with MagicDNS + HTTPS certificates, an auth key URL is https://<name>.<tailnet>.ts.net. Every device needs Tailscale.
Caddy + Let's Encrypt A VPS or a home server with port forwarding DNS A/AAAA record → server, ports 80/443 open Certificates renew automatically.
Home network only Never exposed to the internet A LAN hostname (e.g. nas.local), optionally the LAN IP Caddy's local CA; trust root.crt once per device (the installer prints how).

Docker Compose by hand

services:
  dreyvault:
    image: ghcr.io/dreyfvr/dreyvault:2
    restart: unless-stopped
    environment:
      SERVER_SECRET: ${SERVER_SECRET}        # openssl rand -hex 32
      RP_ID: vault.example.com
      RP_ORIGIN: https://vault.example.com
      SIGNUP: invite
    ports: ["127.0.0.1:3000:3000"]          # put your HTTPS reverse proxy in front
    volumes: [dreyvault_data:/data]
volumes:
  dreyvault_data:

The templates the installer uses are in deploy/compose/ (base.yml + one of cloudflare.yml, tailscale.yml, caddy.yml, lan.yml).

App stores

Platform Template
Umbrel deploy/umbrel/dreyvault/ (umbrel-app.yml + docker-compose.yml)
CasaOS deploy/casaos/docker-compose.yml (Custom Install → Import)
Unraid deploy/unraid/dreyvault.xml (Docker → Add Container → Template)
TrueNAS SCALE deploy/truenas/docker-compose.yml (Apps → Install via YAML)

CasaOS and TrueNAS ship SERVER_SECRET: change-me, which is too short on purpose: the server refuses to start until you set a real one.

First run

  1. Open the URL and create an account. The first account is the admin. Creating it needs the one-time setup token the server prints to its log on first start (docker compose logs dreyvault | grep setup), or the SETUP_TOKEN you set. In Duo each user's first setup needs it. A generated token changes on every restart until setup is complete.
  2. Admin → Backups: set a backup passphrase and keep it somewhere safe (a backup can't be restored without it).
  3. Admin → Accounts: create invite links for other people (with SIGNUP=invite).
  4. Install the browser extension and set the server URL.

The admin manages accounts and server settings but can never read, reset or recover anyone's vault. A forgotten master password can be recovered only with that account's own recovery kit.

Environment

Variable Default Meaning
SERVER_SECRET required ≥ 32 chars (openssl rand -hex 32). Protects server-side secrets (sessions, stored admin credentials). Never change it after setup; back it up with .env.
RP_ID required The vault's hostname, no scheme or port. WebAuthn credentials are bound to it.
RP_ORIGIN required The exact origin users open, e.g. https://vault.example.com.
PUBLIC_URL RP_ORIGIN Base URL used in links (invites, Send links, emails).
SIGNUP invite invite, closed or open. Initial value; the admin panel can change it. The first account can always register (with the setup token).
SETUP_TOKEN generated Optional fixed setup token (≥ 16 chars). Unset: a random one is printed to the log while no account exists (Duo: while a user is unset).
LEGACY_MIGRATION auto auto: accounts still on v1 can upgrade at first sign-in. off: the v1 upgrade path is closed.
PORT / HOST 3000 / 0.0.0.0 Listen address inside the container.
DATA_DIR /data (image) SQLite DB, attachments, Sends, local backups.
WEB_DIST /app/web (image) Built web vault served at /. Unset = API only.
TRUST_PROXY loopback, linklocal, uniquelocal Express trust-proxy setting; the default trusts a proxy on the Docker network.
CORS_ORIGIN empty Extra allowed web origins (comma-separated). Extensions and the desktop app are allowed already.
EXTENSION_ORIGINS empty Pins chrome-extension://… origins (comma-separated); empty allows any Chromium extension.
WEBAUTHN_ORIGINS empty Extra origins accepted for WebAuthn (comma-separated).
SESSION_DAYS 30 Session lifetime.
UPDATE_URL https://dreyvault.abhishekm.work/version.json Where the admin panel checks for updates; off disables it.
RCLONE_CONFIG /data/rclone/rclone.conf (image) rclone config for Google Drive / Dropbox / other remote backup targets.

Backups and restore

Built in (Admin → Backups): nightly by default (at the configured hour; an overdue backup runs at any hour), keeping the newest N backups (retention setting), encrypted with your backup passphrase (Argon2id + chunked AES-256-GCM, .dvbak v2) before they leave the server. Backups are streamed to disk in 1 MiB chunks, so memory use stays small whatever the data size; temporary files go in /data/backups, so that disk needs room for one extra backup. Targets: local disk (an absolute path other than /data/backups), S3/R2, WebDAV, and any rclone remote (Google Drive, Dropbox, …; put rclone.conf at /data/rclone/rclone.conf). After each upload the server reads the copy back from the target and compares its SHA-256; a target is listed on the backup only if that matched. Once a week the server runs a restore test on its own copy (stream-decrypt, integrity_check, attachments present); the panel shows when that last succeeded, and you can verify on demand. Only one backup job runs at a time. A failed scheduled backup or restore test is retried after 6 hours, and admins get at most one failure notification of each kind per day. Vault items inside are still end-to-end encrypted; the passphrase layer also protects server state.

Setting the backup passphrase and downloading a backup from the panel ask for your master password.

Also keep a copy of .env (it holds SERVER_SECRET).

Restore (stop the server first). The backup is unpacked into a staging folder and its database must pass integrity_check before anything is touched; then the previous data is moved to /data/pre-restore-<time>/. Both v2 and older v1 .dvbak files restore.

cd ~/dreyvault && docker compose stop dreyvault
docker compose run --rm -v "$PWD:/backup" -e BACKUP_PASSPHRASE='…' dreyvault \
  node dist/tools/restore.js /backup/dreyvault-<timestamp>.dvbak
docker compose start dreyvault

From a source checkout: cd backend && BACKUP_PASSPHRASE=… npm run restore -- backup.dvbak --data-dir ./data.

Updates

cd ~/dreyvault && docker compose pull && docker compose up -d

Image tags: 2 (latest 2.x, recommended), 2.0, 2.0.0, latest. Database migrations run automatically at start. The admin panel shows when an update is available, and clients tell you when the server is too old for them. Images are signed with cosign (keyless, GitHub OIDC) and carry an SBOM:

cosign verify ghcr.io/dreyfvr/dreyvault:2 \
  --certificate-identity-regexp 'https://github.com/DreyFvR/dreyvault/.github/workflows/release.yml@refs/tags/v.*' \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com

Security model

  • Zero-knowledge. Your devices derive keys from the master password (Argon2id) and encrypt every item (AES-256-GCM) before upload. The server stores ciphertext and an authentication hash, never the password or keys.
  • Safe against a compromised server. Each account signs a manifest of its vault (Ed25519) on every change. Clients check the signature, the version counter and that every item matches, so a server that drops, swaps, replays or rolls back items is detected. Clients also refuse weakened key-derivation settings. Details: docs/CRYPTO_V2.md.
  • One remaining trust point: the web vault's JavaScript is served by your server. The browser extension doesn't load code from the server.
  • Per-account lockouts and per-IP rate limits on sign-in; optional TOTP / security-key second factor; session list with remote sign-out.
  • No telemetry. The only outbound calls are the ones you enable: update check (UPDATE_URL), breach checks (Have I Been Pwned, k-anonymity), SMTP, and backup targets.

Troubleshooting

  • docker compose logs dreyvault. Startup errors name the missing or invalid variable.
  • Biometric sign-in fails: the URL in the address bar must equal RP_ORIGIN and be HTTPS.
  • Extension can't connect: use the exact https:// URL; for home-network-only installs, the device must trust the local root certificate.