这是「Docker 好项目」系列的第 3 期。每期挑一个真正能在自己服务器上跑起来、并且能解决具体问题的项目,给出能直接复制的配置。
你的密码现在散落在几个地方:浏览器自带的密码管理器、手机备忘录、某个记在脑子里的「统一规律」、还有几个网站共用的那同一个密码。
这些方案平时都挺好用,直到某天你换了台设备,或者某个网站泄露了数据库——然后你要花一晚上去改十几个站点的密码。
Vaultwarden 解决的就是这件事。它是 Bitwarden 服务端的 Rust 轻量实现,官方全部客户端照用——手机 App、浏览器扩展、桌面端、CLI,都以为自己在连 Bitwarden 官方服务器,但数据和主密码只存在你自己的机器上。单个容器、默认 SQLite,1 核 512M 的小机器就能长期跑,组织共享、TOTP 验证码、附件、紧急访问这些官方要付费的功能它也都有。

它到底能干什么
先说清楚能力边界,避免装完发现不是自己想要的:
- 兼容性:实现了 Bitwarden 服务端 API,官方客户端(iOS / Android / Chrome / Firefox / Edge / 桌面 / CLI)不用改任何东西,登录时把服务器地址填成你自己的域名即可
- 多用户与共享:可以建组织(Organization)、共享集合、分配只读或可写权限,小团队和家人共用一套没问题
- 自带高级功能:TOTP 验证码存储、文件附件、Send 临时分享、紧急访问(Emergency Access)、HIBP 泄露检测,官方版里这些属于付费项
- 资源占用:单容器,空载内存约 50~256 MB;默认 SQLite,不用额外跑数据库容器
- 数据自持:
db.sqlite3加几个目录就是全部数据,主密码不会离开你的设备
它不做的是:不做浏览器自动填充(那是客户端的活)、不做密码的二次云端备份(丢了就真丢了,见后面的备份章节)、也不是面向几百人企业的合规方案——那种规模该上 PostgreSQL 和正式运维流程。
部署前准备
确认 Docker 和 Compose 插件都在:
docker --version
docker compose version
再确认两件事,Vaultwarden 和别的项目不一样,这两条是硬门槛:
- 一个域名,且能签下证书。后面会解释为什么纯 HTTP 根本用不了,这不是建议。
- 服务器上 80 端口已经被 1Panel 的 OpenResty 占了,所以下面我们会把容器映射到本机
127.0.0.1:8222,再交给反代。华为云这类云主机还要在控制台安全组里放行 80 和 443——你不需要放行 8222,它只对本机可见。
方式一:一行命令跑起来
适合先看看到底是什么东西:
docker run -d --name vaultwarden --restart=unless-stopped \
-p 127.0.0.1:8222:80 \
-v /opt/vaultwarden/data:/data \
-e DOMAIN="https://vault.你的域名.com" \
-e SIGNUPS_ALLOWED=true \
vaultwarden/server:1.37.3
起来后访问 http://127.0.0.1:8222(在服务器上用 curl -I 探活即可,浏览器走这个地址会因为不是安全上下文而登不进去,理由见踩坑清单)。
方式二:docker-compose(推荐)
正式用一定要写成文件,以后升级、迁移都靠它。
mkdir -p /opt/vaultwarden/data && cd /opt/vaultwarden
chmod 700 /opt/vaultwarden
先生成管理员令牌。/admin 面板就靠这一串东西保护,官方现在推荐存 Argon2 哈希而不是明文:
docker run --rm -it vaultwarden/server:1.37.3 /vaultwarden hash
按提示输入两次自定义密码,它会打印一行以 $argon2id$v=19$ 开头的字符串,复制下来。嫌麻烦也可以用 openssl rand -base64 48 生成明文令牌——能用,但启动日志会一直警告你换掉。
新建 docker-compose.yml:
services:
vaultwarden:
image: vaultwarden/server:1.37.3
container_name: vaultwarden
restart: unless-stopped
ports:
# 只监听本机回环,公网入口交给 OpenResty 反代
- "127.0.0.1:8222:80"
volumes:
- ./data:/data
- /etc/localtime:/etc/localtime:ro
environment:
- TZ=Asia/Shanghai
# 必须是用户实际访问的 https 地址,带 https:// 前缀,末尾不要加斜杠
- DOMAIN=https://vault.你的域名.com
# 注意:$ 要写成 $$,官方明确要求,且用单引号包住
- ADMIN_TOKEN='$$argon2id$$v=19$$m=65540,t=3,p=4$你的salt$你的hash'
# 第一次注册完你的账号后,把这里改成 false 并重启
- SIGNUPS_ALLOWED=true
- INVITATIONS_ALLOWED=true
# 附件总容量上限,单位 KB;不写则不限制,个人用建议给个数
- USER_ATTACHMENT_LIMIT=1048576
# 回收站里的条目 30 天后自动删除
- TRASH_AUTO_DELETE_DAYS=30
启动:
docker compose up -d
docker compose logs -f vaultwarden
看到 Rocket has launched from http://0.0.0.0:80 就说明起来了。
这里有一个必做的自检,因为 Argon2 那串东西塞满是
$,最容易在这一步翻车:
bash docker compose config | grep ADMIN_TOKEN输出里应该看到
$argon2id$v=19$...原样保留。如果你写成了单个$,Compose 会把它当变量替换掉,最后你的管理面板要么进不去,要么被一段残缺字符串「保护」着——这属于自找的坑。
几个配置项为什么要这么写
这一段是重点,照抄容易、抄错也容易:
| 配置 | 说明 |
|---|---|
image: ...:1.37.3 |
Vaultwarden 没有 :1.37 这种小版本 tag,也没有 :1 主版本 tag——官方只发布完整版本号和 -alpine 变体(另有 latest / alpine / testing)。所以正确做法是钉死具体版本,升级时手动改数字 |
127.0.0.1:8222:80 |
容器内是 80(镜像里 ROCKET_PORT=80,非容器环境才默认 8000)。Docker 端口映射走 DNAT,优先级高于服务器防火墙,写成 8222:80 等于把密码库直接暴露到公网 |
./data:/data |
数据库、附件、图标缓存、RSA 密钥、后台改过的配置全在这里,这是唯一必须备份的东西 |
/etc/localtime + TZ |
不挂的话容器内是 UTC,日志时间和你看到的对不上 |
DOMAIN |
不只是显示用:附件下载链接、邮件里的链接、U2F/WebAuthn 都依赖它。填错的表现是「能登录但下载附件 404」 |
ADMIN_TOKEN |
不设置时 /admin 面板直接关闭。新版要求 $ 写成 $$ 并用单引号包裹,官方 .env.template 里原文如此 |
SIGNUPS_ALLOWED |
默认是 true,也就是任何人找到你的域名就能注册。全网扫描器发现新上线的 Vaultwarden 通常只需要几个小时 |
INVITATIONS_ALLOWED |
保持 true,这样关掉公开注册后,你仍能在 /admin 里发邀请链接加人 |
USER_ATTACHMENT_LIMIT / ORG_ATTACHMENT_LIMIT |
个人 / 组织的附件总容量上限,单位 KB。不设就是不限制,自建的话建议给个值,防止磁盘被悄悄填满 |
healthcheck |
不用自己写。官方镜像已经内置了 /healthcheck.sh,docker ps 里的健康状况是镜像自带的 |
顺带说清楚两个经常被老教程带偏的地方:
3012端口的 WebSocket 已经没有了。1.31.0 版本就把独立的 WebSocket 端口移除,实时通知并进了主 HTTP 端口。现在还让你映射3012:3012的教程,都是几年前的。- WebSocket 开关现在的变量名是
ENABLE_WEBSOCKET(默认true,通常不用手写)。老教程里的WEBSOCKET_ENABLED已经不在官方.env.template里了,别混用。
在 1Panel 里怎么搞
如果你用 1Panel,两条路:
- 应用商店里搜「Vaultwarden」,能搜到就直接装,安装时注意两点:把对外端口改成非 80 的高位端口、并确认它是只对本机或仅容器网络开放
- 更可控的做法:容器 → 编排 → 创建编排,把上面的
docker-compose.yml整段贴进去,填个名称即可。用./data这类相对路径时,1Panel 会把编排目录当作工作目录
装完去 防火墙 面板确认 8222 没有对外放行——它本来只绑在 127.0.0.1,双重保险。
首次访问:先建管理员账号
访问 https://vault.你的域名.com(DNS 和反代要先配好,见下一节),看到 Bitwarden 的登录界面就对了。
点 Create account,用你的邮箱注册:
- 主密码务必长:这是唯一能解密你全部密码的钥匙,官方也无法帮你找回
- 邮箱可以随便填,不需要真实可达——只有在用邮件邀请、邮件双因素、密码重置时才需要真实邮箱,所以建议还是填真的
- 注册完成后,进
/admin(用前面生成的 ADMIN_TOKEN 登录),把注册关掉
最小可用配置
这一步做完才算「能用了」,具体动作都在 /admin 里:
- 关闭公开注册:
/admin→ 常规设置 → 取消勾选SIGNUPS_ALLOWED→ 保存(也可以改 compose 里的环境变量,但要重启容器) - 开启双因素:在网页版 → 账户设置 → 安全 → 双因素认证,加一个 TOTP(手机上的验证器 App)。主密码 + TOTP 是这套方案的安全底线
- 确认 DOMAIN 正确:
/admin→ 常规设置里核对,是否与你实际访问的地址完全一致 - 改配置的生效顺序:
/admin里改过的设置会写进/data/config.json,优先级高于环境变量。所以如果你明明在 compose 里把SIGNUPS_ALLOWED改成了false却还在被人注册,去/admin里看一眼就知道了 - 装客户端:手机装官方 Bitwarden App → 登录界面左上角设置图标 → 「自托管环境」→ 填
https://vault.你的域名.com(结尾不要加/或/#/)→ 保存后正常登录,账号密码就是刚注册的那套
通知与邮件配置(可选,但建议)
Vaultwarden 的「告警」和监控面板不同,它主要体现在邮件上。配好 SMTP 之后你能获得:
- 新设备登录提醒(登录了没见过的设备,会发邮件给你)
- 邮件双因素 / 密码重置链接
- 在
/admin里发邀请链接加人
不进后台改,直接写在 compose 的环境变量里更清爽:
- SMTP_HOST=smtp.qq.com
- SMTP_FROM=你的邮箱@qq.com
- SMTP_FROM_NAME=Vaultwarden
- SMTP_USERNAME=你的邮箱@qq.com
- SMTP_PASSWORD=你的授权码
- SMTP_SECURITY=starttls
- SMTP_PORT=587
几个关键点:
- 只有
SMTP_HOST和SMTP_FROM都填了,邮件服务才会启用,只填一半是静默不生效的 SMTP_SECURITY和端口是绑定的:starttls配 587,force_tls配 465,off配 25。混着填是最常见的失败原因,比如force_tls+ 587,连接会直接超时- QQ 邮箱、163 这类要填授权码而不是登录密码
- 配完在
/admin→ SMTP 设置里点 Send test email,收到测试邮件再往下走
套上域名和 HTTPS(必做,不是可选)
纯 HTTP 的 Vaultwarden 是不能用的。 这不是安全建议,是技术限制:网页版在浏览器里用 Web Crypto API 做加解密,而浏览器只允许在安全上下文(HTTPS,或 localhost)里访问 crypto.subtle。用 http:// 打开域名时,页面能显示,但一登录就报错,控制台里是类似这样的信息:
Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'importKey')
同时手机 App 和浏览器扩展会直接拒绝连接,提示 This is not a recognized Bitwarden server.。很多人会以为服务端坏了,其实服务端完全正常。
用 1Panel 的话:网站 → 反向代理 → 新建,域名填 vault.你的域名.com,代理地址填 http://127.0.0.1:8222,然后申请 Let's Encrypt 证书、开启 HTTPS。
两个容易被忽略的细节:
- WebSocket 要能透传。1Panel 生成的配置一般已经带了
Upgrade/Connection头;如果实时通知不工作,去网站的配置文件里确认并补上下面三行 - 附件上传默认 1MB 左右就会被 413 拦掉,需要在反代里放开请求体大小(1Panel 高级设置里加
client_max_body_size 128m;)
单独用 Nginx 的话,配置长这样:
server {
listen 80;
server_name vault.example.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name vault.example.com;
# 证书路径按你实际的填
ssl_certificate /etc/letsencrypt/live/vault.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/vault.example.com/privkey.pem;
# 不放开的话,稍大一点的附件上传会 413
client_max_body_size 128m;
location / {
proxy_pass http://127.0.0.1:8222;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# 实时通知走的是主端口,这几行别省
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 300s;
}
}
反代之后,日志里的客户端 IP 默认按 X-Real-IP 头来读(IP_HEADER,默认值就是 X-Real-IP),并且只信任来自本机/内网的请求(IP_HEADER_TRUSTED_PROXIES,默认 local)。登录失败限流和 /admin 限流都依赖这个 IP,所以上面那两个头一定要带上,否则所有人共用一个「127.0.0.1」的额度。
踩坑清单
- 别用
:latest,也别用 1.36.x。1.37.0 一次修了 8 个安全公告(包括图标接口的 SSRF、未认证 WebSocket 泛洪 DoS),并且官方说明 1.37 是配合 2026.7.0+ 客户端所必需的;1.37.1 又修了 1.37.0 弄坏的组织邀请。直接上1.37.3 - 纯 HTTP 一定登不进去,报
importKey之类看不懂的错。先看地址栏是不是 https - 自签证书同样会翻车。手机端不接受未知 CA,除非每台设备都装上你的证书,否则别折腾
- 忘了关注册。这是自建密码库最危险的低级失误,装完第一件事就是
SIGNUPS_ALLOWED=false - 删容器之前先看清楚数据卷。
docker compose down -v里的-v会把命名的数据卷一起删掉;用./data绑定挂载时不受影响,这也是本方案坚持用相对路径而不是命名卷的原因 - RSA 密钥不能丢。
/data/rsa_key.pem和rsa_key.pub.pem丢了的话,恢复备份后所有客户端会话和双因素都要重来,光备份db.sqlite3是不够的 - 别把图标抓取想得太无害。图标服务会由你的服务器主动去访问外部 URL(
ICON_SERVICE),历史上出过 SSRF 问题。如果不需要给登录条目自动抓 favicon,可以调ICON_CACHE_TTL或直接关掉外部抓取 - 主密码没有找回通道。Vaultwarden 的管理员可以重置用户密码,但那会清空该用户的保险库内容。所以:务必开 2FA,并定期在客户端里导出一份加密备份放别处
备份
自建密码管理器最不能省的一步。要备份的是整个 ./data 目录,不是只有那个 .sqlite3 文件。
SQLite 正在被写入时直接 cp 可能拿到一个写坏的快照,可以停容器再打包:
cd /opt/vaultwarden
docker compose stop vaultwarden
tar czf vaultwarden-backup-$(date +%F).tar.gz data/
docker compose start vaultwarden
停不了机(或者不想中断同步)就用 SQLite 自带的在线备份:
docker compose exec vaultwarden sqlite3 /data/db.sqlite3 ".backup '/data/backup-$(date +%F).sqlite3'"
然后把这一个文件和 data/attachments/、data/rsa_key*、data/config.json 一起打包。配合 1Panel 的计划任务每周跑一次,产物丢到对象存储或另一台机器上——备份和源数据在同一块盘上,那不叫备份。
还有一条比备份本身更重要:每隔一段时间真的恢复一次试试。起一个临时容器挂上备份文件,用客户端登录一次,能进去才算备份有效。
小结
Vaultwarden 是这个系列里少见的「越用越离不开」的项目:把散落在浏览器、备忘录和脑子里的密码,收进一个由你自己控制的加密保险库,而你的手机、浏览器完全不用改变使用习惯。代价是半小时的部署时间,外加一条必须记住的纪律——备份。
适合:有域名和一台常开小机器的个人 / 家庭 / 小团队;想把密码数据从第三方拿回来的人;已经在自建博客、网盘、监控,顺手把密码库也收了的人。
不适合:不愿意为「备份」这件事承担责任的用户——密码库丢了没有客服可以找;也不适合几十人以上、需要审计与合规的组织,那个规模请用官方 Bitwarden 自托管或商业方案。
下期继续聊另一个 Docker 项目。
项目地址:https://github.com/dani-garcia/vaultwarden
官方文档 / Wiki:https://github.com/dani-garcia/vaultwarden/wiki
讨论区:https://vaultwarden.discourse.group/