Vaultwarden
Vaultwarden is a self-hosted password manager compatible with Bitwarden clients — browser extensions, mobile apps, and the desktop client. It can hold access credentials to everything else you run; treat setup and backup accordingly.
Last run on a host 9 Nov 2026 · data restored from a backup
Pinned at 1.37.5 (released 1 Nov 2026) · the newest upstream release
In the upstream images: 1 CRITICAL · 28 HIGH with a published fix — per image
To try it: Docker, nothing else. See Try it locally.
To deploy it behind the proxy: Traefik running, a domain, and SMTP credentials (email verification is on by default).
Try it locally
Section titled “Try it locally”On your own machine, with no proxy, domain or certificate. From apps/vaultwarden/:
cp .env.local.example .env.local
# The secrets ship empty; generate the ones this stack needs{ echo "DB_PASSWORD=$(openssl rand -hex 32)" echo "DB_ROOT_PASSWORD=$(openssl rand -hex 32)"} >> .env.local
docker compose -f docker-compose.local.yml --env-file .env.local up -dThen open http://localhost:8080. Stop it again with the same command and down.
Installation
Section titled “Installation”From apps/vaultwarden/:
-
Terminal window cp .env.example .env -
Set your domain in
.env:APP_TRAEFIK_HOST=vault.example.com -
Set SMTP — required before the first account can verify its email:
VW_SMTP_HOST=smtp.example.comVW_SMTP_FROM[email protected]VW_SMTP_PORT=587VW_SMTP_SECURITY=starttlsVW_SMTP_USERNAME=your-smtp-userVW_SMTP_PASSWORD=your-smtp-password -
Generate database passwords. Hex is a convention here, not a requirement: the password ends up inside a connection URL, and hex avoids having to URL-encode characters that would otherwise break it.
Terminal window sed -i "s|^DB_PASSWORD=.*|DB_PASSWORD=$(openssl rand -hex 32)|" .envsed -i "s|^DB_ROOT_PASSWORD=.*|DB_ROOT_PASSWORD=$(openssl rand -hex 32)|" .env -
Optional: the admin panel.
VW_ADMIN_TOKENships empty, which keeps the panel at/adminoff. Set it only when you need the panel, and never to a plain word — Vaultwarden accepts a plain-text token as it stands, and a plain token in.envis readable by anything that can read the file or inspect the container. Generate a hash instead:Terminal window docker compose run --rm --no-deps vaultwarden-app /vaultwarden hashCopy the full
$argon2id$...output, then paste it into.envwith every$doubled (Compose escaping):VW_ADMIN_TOKEN=$$argon2id$$v=19$$m=65540,t=3,p=4$$...your-hash...This stack has no Docker Secrets. Every credential it uses is read from
.envand passed to the containers as an environment variable, including the database passwords, the admin token, and the SMTP and push values once those are set. Anything passed that way appears indocker inspectand indocker compose config— treat both outputs as credential material and redact them before pasting either anywhere, and keep.envas tightly permissioned as a secrets directory. -
Allow signups for your first account — you’ll turn this off again right after:
VW_SIGNUPS_ALLOWED=true -
Start:
Terminal window docker compose up -ddocker compose logs -fWait for
Starting Vaultwardenwith no errors — MariaDB has a health check Vaultwarden waits on.
Verify
Section titled “Verify”docker compose ps # both containers healthycurl -sI https://your-domain/alive # expect 200 OKOpen https://your-domain — the login page should load over TLS.
Create your account and lock signups
Section titled “Create your account and lock signups”-
Click Create Account, register, and verify your email.
-
In
.env, turn signups back off:VW_SIGNUPS_ALLOWED=falseRecreate the app container so it loads the changed environment:
Terminal window docker compose up -d --force-recreate vaultwarden-appConfirm it took effect against the container rather than against the page:
Terminal window docker compose exec vaultwarden-app env | grep SIGNUPS_ALLOWED # expect falseThen reload the login page — “Create Account” should be gone.
-
With the admin panel switched on (installation step 5), set at
https://your-domain/admin:Setting Value Allow new signups false Allow password hints false Block non-global IPs true Disable 2FA remember true -
Enable 2FA on your account: Settings → Security → Two-step login.
Going further
Section titled “Going further”Public access instead of Tailscale-only
Section titled “Public access instead of Tailscale-only”The installation above uses the default, APP_TRAEFIK_ACCESS=access-vpn — the vault is reachable only over Tailscale/VPN. Setting APP_TRAEFIK_ACCESS=access-public makes it reachable from the open internet instead. That’s a deliberate change to your attack surface, not a default — decide it before exposing real credentials.
Backup before real use
Section titled “Backup before real use”Do not add real credentials before a backup exists.
What to back up: volumes/mysql/ (database), volumes/data/attachments/, volumes/data/rsa_key.* (signing keys — without these, sessions break on restore even with an intact database), volumes/data/sends/.
docker exec -e MYSQL_PWD="$(grep DB_ROOT_PASSWORD .env | cut -d= -f2)" \ vaultwarden-db mariadb-dump -u root vaultwarden > backup-$(date +%Y%m%d).sqlThe command above is a one-off dump. To have it run on a schedule alongside the data directories, and to hold the archives somewhere the server cannot reach, see backup and restore — Borgmatic on the host, with the database dump wired in as a pre-backup hook.
The three parts have to be restored together, from the same point in time.
The database alone is not enough: without rsa_key.* every session breaks even
though the vault contents are intact, and without the attachments directory
entries lose their files.
Updates
Section titled “Updates”- Check the release notes for breaking changes.
- Back up all three parts together — the database dump,
volumes/data/rsa_key.*andvolumes/data/attachments/. A database-only snapshot is not something you can roll back to: restoring it without the matching signing keys breaks every session even though the vault contents are intact. - Update
APP_TAGin.env. -
Terminal window docker compose pulldocker compose up -d - Verify
/aliveand log in.
Troubleshooting
Section titled “Troubleshooting”Container not starting: check docker compose logs vaultwarden-app and docker compose logs db. Common causes: MariaDB not yet healthy (the app waits for it), or incorrect $$ escaping in VW_ADMIN_TOKEN.
Login page not loading but the container is healthy: check Traefik logs and confirm APP_TRAEFIK_HOST matches your DNS record exactly.
Email verification not arriving: recreate the app after changing the SMTP
values — docker compose up -d --force-recreate vaultwarden-app — and confirm
the container has them:
docker compose exec vaultwarden-app sh -c 'env | grep -E "^SMTP_HOST=|^SMTP_FROM=|^SMTP_PORT=|^SMTP_SECURITY=|^SMTP_USERNAME="'docker compose logs vaultwarden-app --tail=200 | grep -iE "smtp|mail|error|lettre"With the admin panel on, Admin → Users → Invite User is an end-to-end test. Mail that lands in junk points at the sender domain’s SPF, DKIM and DMARC records.
Push notifications do not sync: Unexpected push token received from bitwarden server: error decoding response body means EU credentials (a bitwarden.eu registration) without the EU relay. Set VW_PUSH_RELAY_URI=https://api.bitwarden.eu and VW_PUSH_IDENTITY_URI=https://identity.bitwarden.eu in .env and recreate the container.
Admin → Diagnostics reports Header 'x-frame-options' is present: the proxy is setting the frame header Vaultwarden controls per route. Confirm .env still has APP_TRAEFIK_HEADERS=headers-application-owned.
Admin → Diagnostics reports HTTP Response validation: Error: the address in use does not match DOMAIN, which the compose file sets to https://${APP_TRAEFIK_HOST}, or HTTPS is not enforced end to end.
/admin fails in the normal browser but works in a private window: clear the site data and check browser extensions before suspecting the server.
Repository files
Section titled “Repository files”apps/vaultwarden/README.md— full setup and admin panel settingsapps/vaultwarden/.env.exampleapps/vaultwarden/UPSTREAM.md— upstream notes, backup, and upgrade steps
Official sources
Section titled “Official sources”© 2026 SecDockBlueCode Apache-2.0Site content CC BY-NC 4.0