Vaultwarden
Bitwarden-compatible password manager, written in Rust.
The stack
Generated output
services:
vaultwarden:
image: docker.io/vaultwarden/server:1.32.5-alpine@sha256:76d46d32ba4120b022e0a69487f9fd79fc52e2765b1650c5c51a5dd912a3c288
restart: unless-stopped
environment:
DOMAIN: https://${VAULTWARDEN_HOST}
SIGNUPS_ALLOWED: "false"
ADMIN_TOKEN: ${ADMIN_TOKEN}
WEBSOCKET_ENABLED: "true"
SENDS_ALLOWED: "true"
EMERGENCY_ACCESS_ALLOWED: "true"
PASSWORD_ITERATIONS: "600000"
SHOW_PASSWORD_HINT: "false"
LOG_FILE: /data/vaultwarden.log
LOG_LEVEL: warn
ROCKET_PORT: "8080"
volumes:
- vaultwarden-data:/data
healthcheck:
test:
- CMD
- wget
- -qO-
- http://127.0.0.1:8080/alive
interval: 30s
timeout: 5s
retries: 3
volumes:
vaultwarden-data: {}
# Caddyfile for Vaultwarden
# Replace vault.example.com with your real domain. Caddy issues TLS automatically.
vault.example.com {
encode zstd gzip
header {
Strict-Transport-Security "max-age=31536000;"
}
reverse_proxy /notifications/hub vaultwarden:8080 {
header_up Host {host}
header_up X-Real-IP {remote_host}
}
reverse_proxy vaultwarden:8080 {
header_up Host {host}
header_up X-Real-IP {remote_host}
}
}
# Traefik labels for Vaultwarden
# Merge into the vaultwarden service in docker-compose.yml.
# Assumes an external Traefik network named "proxy" and a
# certresolver named "letsencrypt". Replace vault.example.com.
services:
vaultwarden:
labels:
traefik.enable: "true"
traefik.http.routers.vaultwarden.rule: Host(`vault.example.com`)
traefik.http.routers.vaultwarden.entrypoints: websecure
traefik.http.routers.vaultwarden.tls: "true"
traefik.http.routers.vaultwarden.tls.certresolver: letsencrypt
traefik.http.services.vaultwarden.loadbalancer.server.port: "8080"
networks:
- proxy
- default
networks:
proxy:
external: true
Replace vault.example.com with your domain. Generate secrets below.
Environment
.env with generated secrets
Secrets are generated in your browser via crypto.getRandomValues. Nothing is sent anywhere.
Server-rendered .env template
# Public hostname Vaultwarden is served on. MUST be HTTPS. VAULTWARDEN_HOST=vault.example.com # Argon2-hashed admin token. Vaultwarden expects the hash, not the plaintext. Generate via `docker run --rm vaultwarden/server /vaultwarden hash` after first boot — the value below is a placeholder. ADMIN_TOKEN=
About
What is Vaultwarden?
Vaultwarden is an unofficial, Rust-based Bitwarden server. It implements the same API as the official Bitwarden server, so all official clients (browser, mobile, desktop, CLI) work against it, but the resource footprint is tiny — a Raspberry Pi handles it without breaking a sweat. The trade-off vs. official Bitwarden is no paid enterprise features and a smaller security audit surface; for a household or small team that is the right trade. This stack runs a single Vaultwarden container with SQLite (no separate database needed), persistent storage, and disabled signups by default — you generate an admin token, finish first-time setup, then turn signups back on or invite users individually. WebSocket notifications and ICON proxying are enabled. The compose file binds nothing to the host: the reverse proxy at `vault.example.com` is the only entry point.
Requirements
Before you start
- 256 MB RAM (yes, really — Rust footprint is small).
- Docker 24+ and Docker Compose v2.
- A real domain with HTTPS. Bitwarden clients refuse plain HTTP.
- Persistent disk for the vaultwarden-data volume — that is your vault.
Deploy
How to deploy
- Set `VAULTWARDEN_HOST` in `.env` to your hostname (e.g. `vault.example.com`).
- Generate `ADMIN_TOKEN` via the "Generate secrets" button, then hash it: `docker run --rm vaultwarden/server /vaultwarden hash` and paste the Argon2 string back into `.env`.
- Start the stack: `docker compose up -d`.
- Visit `https://vault.example.com/admin`, paste the original (un-hashed) admin token, and create your first user.
- In the admin panel, leave `SIGNUPS_ALLOWED=false` and invite additional users by email instead.
- Install the Bitwarden app, point Server URL at `https://vault.example.com`, log in.
Errors
Common errors & fixes
Bitwarden client says "We could not process your request" on login.
Vaultwarden requires a valid HTTPS certificate. Self-signed certs are rejected by mobile clients. Make sure the reverse proxy issues a real cert (Caddy does this automatically with a public domain).
WebSocket notifications never fire — clients only update on manual sync.
The reverse proxy is not forwarding the `/notifications/hub` WebSocket. The Caddyfile in this stack handles it; for Traefik, ensure the router accepts WebSocket upgrades (default behavior).
Admin page redirects in a loop or shows "Invalid Admin Token".
You stored the plaintext token in `ADMIN_TOKEN` instead of the Argon2 hash. Run `docker run --rm vaultwarden/server /vaultwarden hash`, paste the resulting `$argon2id$...` string into `.env`, restart the container.
After a host reboot the vault is empty.
You used a bind mount to a path that did not persist, or removed the named volume. The vault lives in `/data/db.sqlite3` inside the container — make sure the `vaultwarden-data` volume is intact and back it up regularly.
Limitations
Honest limitations
- Not an official Bitwarden product — no commercial support, no SOC2.
- No high-availability clustering; SQLite is single-node by design.
- Some enterprise features (Duo SSO, advanced policies) are partial or absent.
FAQ
Frequently asked
Is Vaultwarden safe to use as my real password manager?+
It is widely used and actively maintained, and your vault is end-to-end encrypted with your master password — the server only ever sees ciphertext. The trade-off vs. official Bitwarden is operational: you carry the backup and uptime responsibility yourself.
Do I really need to disable signups?+
Yes, by default. If your instance is reachable from the internet and signups are open, anyone can register an account and use your server. Disable, then invite by email from the admin panel.
Why is ADMIN_TOKEN special?+
It grants access to the admin panel which can manage users, send invites, and read logs. Vaultwarden expects it pre-hashed with Argon2 so a leak of `.env` is not a direct compromise — generate with the `vaultwarden hash` command.
How do I back this up?+
Back up the entire `vaultwarden-data` volume — it contains `db.sqlite3`, attachments, sends, and config. Stop the container during backup or use `sqlite3 .backup` to get a consistent snapshot.
Can I migrate from official Bitwarden later?+
Yes, in either direction — both speak the same export/import format. Export vault JSON from one, import into the other.
Related