Vaultwarden is a lightweight, Bitwarden-compatible server. You get all the official Bitwarden apps and browser extensions, pointed at your own infrastructure instead of Bitwarden's cloud.
Here is how to run it properly, and how to migrate an existing instance without losing anything.
If you already run a pile of Docker containers on a NAS, it is tempting to add Vaultwarden as one more.
Do not. Your password vault then shares fate with everything else on that host. Container churn. Experiments. Reboots. It may also sit outside whatever backup job protects your real infrastructure.
Give it its own small VM or container, on its own backup schedule, isolated from everything else.
services:
vaultwarden:
image: vaultwarden/server:latest
restart: unless-stopped
ports:
- "80:80"
volumes:
- ./data:/data
That is most of it. Vaultwarden is a single lightweight binary and uses SQLite by default, so there is no external database to run.
Four things actually need to move.
db.sqlite3 plus -wal and -shm. The vault database. Stop the
source container first so SQLite checkpoints its write-ahead log cleanly before you copy.rsa_key.pem. The cryptographic identity. Lose this and every existing client session and
JWT is invalid.config.json. Vaultwarden's persisted runtime config, which overrides environment
variables. Copy it verbatim. Do not rebuild it by hand. It holds real SMTP credentials for
invite and 2FA emails that are easy to get subtly wrong.attachments/ and sends/. Real user data. Skip icon_cache/ and
tmp/, they are pure cache.rsync, and that breaks over SSH.
Run rsync from a remote host over SSH against a NAS like this and it fails immediately with
Permission denied, please try again. and a closed connection. The vendor's OS is correctly
blocking a setuid-root binary from being called non-interactively. Use a tar stream over SSH
instead.
ssh <nas> "sudo tar -C /path/to/vaultwarden -cf - --exclude=icon_cache --exclude=tmp ." \
| tar -C <local-staging> -xf -
Check whether your domain already goes through a reverse proxy that terminates TLS.
If it does, cutover is one edit. Point that rule's backend at the new address. No DNS change. No new certificate.
This works for any already reverse-proxied service. Check for the proxy before assuming a migration needs new TLS or DNS work.
Confirm the new instance is healthy before you flip it. Check the logs for a clean startup. Hit its
health endpoint with GET /alive.
Then do a real login against the new backend. The migration is not done until that works.
Leave the old instance stopped, not deleted, and leave its data directory alone for a while.
It costs nothing and it is your rollback if something surfaces later that the migration missed.