部署配方 · 密钥与身份
Vaultwarden:放在家里的密码库
这是机架上唯一一个「消失就是真损失」的服务,也是唯一一个备份比在线率更重要的服务。Vaultwarden 是用 Rust 重写的、兼容 Bitwarden 服务端的实现:协议一致,所以官方浏览器扩展和手机 App 都能直连,而它占 90MB 内存,而不是官方服务端的好几个 GB。
| 容器镜像 | vaultwarden/server:1.33.2 |
|---|---|
| 宿主端口 | :8083/tcp |
| 数据目录 | /srv/homelab/vaultwarden/data |
| 内存预留 | 90 MB |
| CPU 上限 | 0.20 vCPU (cpus: "0.20") — Argon2 KDF runs only at login |
| 更新节奏 | quarterly, or immediately for a security release. Vaultwarden tracks the Bitwarden clients, which change their API expectations a few times a year |
| ARM 兼容 | arm64 native image; Argon2id login on a Pi 4 takes about 0.6 seconds at the default KDF settings |
| 宿主 | 容器 | proto | 暴露 | 用途 |
|---|---|---|---|---|
| :8083 | 80 | tcp | 仅局域网 | web vault, the browser extension API and the mobile apps |
部署步骤
建数据目录并生成管理令牌
下面把管理页面整个关掉了,但 CLI 做用户管理时仍然要用到这个令牌。生成一次,写进 env 文件。
run sudo mkdir -p /srv/homelab/vaultwarden/data sudo chown -R 1000:1000 /srv/homelab/vaultwarden echo "VW_ADMIN_TOKEN=$(openssl rand -base64 48)" >> /srv/homelab/.env先开放注册建号,再关掉
上面的配置里注册是关闭的,因为那是长期该有的状态。初次搭建时先用 SIGNUPS_ALLOWED=true 启动一次,注册完两个账号再改回去重建。
run docker compose up -d vaultwarden docker exec vaultwarden /vaultwarden --version # 在 https://vault.lan 注册,然后: docker compose up -d --force-recreate开启双因素,把恢复码离线存好
恢复码是手机丢失后唯一的回路。打印出来,或者手写在纸上。把它存在它自己保护的那个密码库里,是经典错误。
run # 账号设置 -> 安全 -> 两步登录 -> 验证器应用 # 然后查看恢复码并抄下来把客户端指过来,确认双向同步
服务器地址要在浏览器扩展和手机 App 里都设,不能只在网页端设。真正重要的是客户端——三种界面里,网页端是最少用的那个。
run # 扩展 -> 设置 -> 自建环境 -> https://vault.lan # 然后在手机上新增一条测试记录,确认扩展里能看到装好每晚的导出,并手动恢复一次
导出就是数据目录的一个 tar 包。先手动跑一次、手动恢复一次,然后才放进 cron。
run docker exec vaultwarden sqlite3 /data/db.sqlite3 ".backup '/data/db-backup.sqlite3'" tar czf /srv/homelab/backup/vaultwarden-$(date +%F).tgz -C /srv/homelab/vaultwarden data
为什么不用托管免费版
托管的免费版确实很好用,用它一点也不丢人。要自建这个服务的理由在于:密码库是其它一切所依赖的东西——路由器密码、恢复码、SSH 密钥都在里面——而放在你家某台机器上的密码库,是一个当手机、邮箱和托管账号同时被锁在外面时,你依然打得开的密码库。
相应的代价是义务:密码库的备份是这台机架上最重要的文件。它每晚备份、拷到 NAS,恢复材料还有一份打印件放在抽屉里。
只走局域网和 WireGuard,不上公网
vault.lan 在局域网内解析,在外面通过 WireGuard 隧道同一个主机名也能解析。路由器上不做任何转发,所以密码库不存在公开登录页——这是「每天被扫描的服务」和「根本不会被扫到的服务」之间的区别。
客户端对此适应良好:浏览器扩展会缓存密码库,隧道一通就同步,手机 App 同理。需要知道的局限是:在飞机上新增的密码要等三天后才同步——这是真实存在的限制,也是离线导出功能存在的理由。
Argon2 参数与树莓派的登录开销
Vaultwarden 在登录时用 Argon2id 派生密码库密钥。Pi 4 在默认参数下大约六百毫秒——正是这个开销让被偷走的数据库难以破解,也正是它让两个用户登录时 CPU 的尖峰在 Grafana 上看得见。
提高 KDF 迭代次数会让密码库更难破解,也会让所有人的登录变慢,包括每一台在 App 更新后需要重新派生密钥的设备。这个体量下默认值就是对的取舍;要改,值得把理由写下来。
docker exec vaultwarden /vaultwarden hash --preset owasp
# 需要轮换时生成新的 ADMIN_TOKEN 哈希真正的交付物是恢复演练
一个从没被恢复过的密码库备份是信念,不是备份。演练二十分钟,一年做两次:停容器、把数据目录挪开、恢复昨晚的副本、启动、从某台设备登录。
要在同一台派上做,把生产数据挪到一边,而不是找一台备用机器。目的是证明「光靠数据目录就够」——因为固态盘坏掉时,你手上正好就只有它。
docker compose stop vaultwarden
sudo mv /srv/homelab/vaultwarden/data /srv/homelab/vaultwarden/data.old
sudo tar xzf /srv/homelab/backup/vaultwarden-$(date -d yesterday +%F).tgz -C /srv/homelab/vaultwarden
docker compose up -d vaultwarden && docker logs --tail 10 vaultwardencompose 文件
整份文件放在 /srv/homelab/vaultwarden/compose.yaml。标签固定版本号,不用 latest——树莓派上回滚比升级麻烦得多。
services:
vaultwarden:
image: vaultwarden/server:1.33.2
container_name: vaultwarden
restart: unless-stopped
ports:
- "8083:80/tcp"
environment:
TZ: Asia/Shanghai
DOMAIN: https://vault.lan
SIGNUPS_ALLOWED: "false"
INVITATIONS_ALLOWED: "false"
ADMIN_TOKEN: ${VW_ADMIN_TOKEN}
SHOW_PASSWORD_HINT: "false"
ROCKET_PORT: "80"
SENDS_ALLOWED: "true"
WEB_VAULT_ENABLED: "true"
volumes:
- /srv/homelab/vaultwarden/data:/data
read_only: true
tmpfs:
- /tmp
security_opt:
- no-new-privileges:true
networks: [rack]
networks:
rack:
external: true加固清单
- 关闭注册,别人发现了这个实例也建不了账号
- 完全关闭管理面板;用户管理通过 CLI 加令牌完成,令牌放在 env 文件里
- 密码库只在局域网和 WireGuard 内可达,从不做端口转发——公网上的密码管理器登录页就是一个靶子
- 两个账号都强制开启双因素,恢复码打印出来离线保存
- 除数据卷外,容器以只读方式运行
备份方案
SQLite 数据库连同附件和 send 目录每晚复制一次,另外还有一份拷进手机上的离线密码管理器。这第二份不是偏执:这个服务存在的全部意义,就是它装着你无法通过其它途径找回的那些凭据。
上线后验证
- 手机在移动网络下经 WireGuard 能打开密码库,并看到在局域网内新增的条目
- 在派上网页端登录大约一秒,这是 Argon2id 的开销而不是问题
- 昨晚的归档能恢复进一个运行中的实例,且账号照常登录
- 登录页上没有注册入口,管理页面返回 404
踩过的坑
- SIGNUPS_ALLOWED 留在 true,就是自建密码库里出现陌生人的方式。镜像里它的默认值是 true,这也是这里显式设成 false 的原因。
- 把双因素恢复码存进它保护的那个密码库里,意味着手机和密码库会同时丢失。写在纸上,放抽屉。
- 数据目录必须对容器的 uid 可写。只读式的 rootless 配置如果漏了可写卷,会得到一个能启动、但每次写入都以数据库错误失败的容器。
- 客户端都有本地缓存,服务器不可达时会照样显示一份过期的密码库。如果某处改动在第二台设备上没出现,先检查客户端的服务器地址,别急着认定服务端坏了。
硬件与选型问答
- 自建密码管理器安全吗?
- 它和那台机器、以及那份备份一样安全。它防的威胁——托管服务商被入侵——真实但罕见;它引入的威胁——没打补丁的派、被转发的端口、放在共享盘上的备份——反而更可能发生。放在局域网里、保持更新、备份离线保管,它就是净收益。
- 能让全家共用密码吗?
- 可以,通过组织和集合。建一个组织,把共用条目——wifi 密码、流媒体账号、门锁密码——放进两个账号都能看到的集合,私人条目留在各自的私人库里。无论怎样,双因素的恢复码都保持个人所有并离线保存。
- 派坏掉会怎样?
- 把数据目录恢复到新卡或新固态盘上,启动容器,每个客户端会同步回它们各自的位置。你甚至可以在没有服务器的情况下读出密码库内容,因为每个客户端都保留着一份加密的本地副本——手机 App 的导出就是那扇逃生门,值得在需要它之前先测一次。