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
Installer (recommended)
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
- 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 theSETUP_TOKENyou set. In Duo each user's first setup needs it. A generated token changes on every restart until setup is complete. - Admin → Backups: set a backup passphrase and keep it somewhere safe (a backup can't be restored without it).
- Admin → Accounts: create invite links for other people (with
SIGNUP=invite). - 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_ORIGINand be HTTPS. - Extension can't connect: use the exact
https://URL; for home-network-only installs, the device must trust the local root certificate.