vaultwarden
Installation
SKILL.md
Identity
- Image:
vaultwarden/server(Docker Hub) - Config: Environment variables passed to the container (no separate config file)
- Data dir:
/datainside container (mount a host volume here) - Database: SQLite at
/data/db.sqlite3by default; PostgreSQL or MySQL optional - Port: 80/tcp (HTTP inside container — always put HTTPS termination in front)
- Admin panel:
/adminpath (disabled by default; requiresADMIN_TOKENenv var) - Websocket port: 3012/tcp (live sync; must be proxied alongside port 80)
Key Operations
| Operation | Command |
|---|---|
| Check container status | docker ps | grep vaultwarden |
| View live logs | docker logs -f vaultwarden |
| View last 100 log lines | docker logs --tail 100 vaultwarden |
| Restart container | docker restart vaultwarden |
| Stop / start | docker stop vaultwarden / docker start vaultwarden |
| Inspect environment variables | docker inspect vaultwarden | python3 -m json.tool | grep -A1 Env |
| Backup data directory | tar czf vaultwarden-backup-$(date +%Y%m%d).tar.gz -C /opt/vaultwarden data/ |
| Restore from backup | docker stop vaultwarden && tar xzf vaultwarden-backup-<date>.tar.gz -C /opt/vaultwarden && docker start vaultwarden |
| Enable admin panel | Set ADMIN_TOKEN env var and recreate container |
| Generate admin token (argon2) | docker run --rm -it vaultwarden/server /vaultwarden hash --preset owasp |
| List users (admin API) | curl -s -H "Authorization: Bearer <token>" https://vault.example.com/admin/users |
| Force sync all clients | Admin panel → Users → click user → Deauthorize Sessions |
| Update container image | docker pull vaultwarden/server && docker compose up -d |
| Disable new signups | Set SIGNUPS_ALLOWED=false and recreate container |
| Check email config | Send test email via admin panel → Diagnostics → SMTP → Send test email |
| SQLite integrity check | docker exec vaultwarden sqlite3 /data/db.sqlite3 'PRAGMA integrity_check;' |
Expected Ports
- 80/tcp — HTTP inside the container (never expose this directly; terminate TLS upstream)
- 3012/tcp — WebSocket for live sync (must be proxied; clients will poll without it but sync is delayed)
- Bitwarden/Vaultwarden clients refuse plaintext HTTP — HTTPS with a valid cert is required
- Verify container is listening:
docker port vaultwarden
Health Checks
docker inspect --format='{{.State.Status}}' vaultwarden→runningcurl -sf https://vault.example.com/alive→ HTTP 200 (Vaultwarden health endpoint)curl -sf https://vault.example.com/api/config→ JSON response withversionfield
Common Failures
| Symptom | Likely cause | Check / Fix |
|---|---|---|
| Clients show "Invalid server URL" or refuse to connect | HTTP instead of HTTPS — clients enforce TLS | Set up a reverse proxy with a valid cert; DOMAIN env var must be the HTTPS URL |
| Admin panel returns 404 | ADMIN_TOKEN not set or container not recreated after adding it |
Set ADMIN_TOKEN, then docker compose up -d to recreate (not restart) |
| Email invites / 2FA emails not delivered | SMTP not configured or wrong credentials | Check SMTP_* env vars; use admin panel Diagnostics → Send test email |
| Attachment uploads fail or are missing | /data volume not mounted or wrong path |
Verify volume mount in compose file; check DATA_FOLDER env var matches mount |
| "Database is locked" in logs | Concurrent SQLite writes (e.g., multiple containers or backup tool hitting DB live) | Stop container before backup; or migrate to PostgreSQL for multi-user deployments |
| Mobile push notifications not working | PUSH_ENABLED not set or Bitwarden relay account not configured |
Register at https://bitwarden.com/host/; add PUSH_INSTALLATION_ID and PUSH_INSTALLATION_KEY |
| WebSocket errors in browser console | Port 3012 not proxied or WebSocket upgrade headers missing | Add WebSocket proxy block to nginx/Caddy config; confirm port 3012 forwarded |
Pain Points
- HTTPS is mandatory: Bitwarden-compatible clients enforce HTTPS at the protocol level. A self-signed certificate will cause connection failures unless the cert is manually trusted on every device. Use Let's Encrypt via Caddy or Certbot.
- Admin panel is off by default: The
/adminendpoint only activates whenADMIN_TOKENis set. Use the argon2 hash form (vaultwarden hash) rather than a plaintext token — plaintext works but logs a warning every startup. - Container must be recreated (not restarted) after env var changes:
docker restartdoes not re-read environment variables. Changes to the compose file requiredocker compose up -dto recreate the container. - SQLite is sufficient for personal or small-team use: The single-writer limitation rarely matters at low concurrency, but if you see "database is locked" under normal use, migrate to PostgreSQL via
DATABASE_URL. - Regular
/databackups are non-negotiable: The entire Vaultwarden state (vault DB, attachments, config, RSA keys) lives in/data. A missing or stale backup means unrecoverable vault loss. Automate backups with a cron job or a sidecar container. - Disable signups after initial user creation:
SIGNUPS_ALLOWED=true(the default) lets anyone register. SetSIGNUPS_ALLOWED=falseimmediately after creating your accounts, or useSIGNUPS_DOMAINS_WHITELISTto restrict by email domain. DOMAINmust match the actual HTTPS URL: Vaultwarden uses this for generating invitation links, TOTP URIs, and push notification callbacks. A mismatch causes broken invite emails and client errors even when login works.
References
See references/ for:
docker-compose.yml.annotated— complete compose file with every environment variable explaineddocs.md— official documentation and community links
Related skills