Use Docker and Traefik to deploy BitWarden on VPS

不记得从何时开始,密码管理软件成为了我生活中不可或缺的一部分。在尝试过多种软件之后,我一开始使用的是开源的KeePass。但是可能也正因为开源的原因,其对应的软件以及浏览器插件需要靠志愿者来支持,因此质量相对就参差不齐。后来也尝试过其他的软件比如说SafeinCloud,这个软件对比Keepass已经要方便不少。后来有一天我在Reddit看沙雕贴的时候无意看到有人提了一嘴BitWarden,一试之下马上就转用它了。特别是它提供self-hosting的选项更是点燃了我学习使用Docker的热情。可惜的是官方self-hosting的镜像对于vps的性能要求较高,我的小服务器完全没法承受。还好有大神在官方镜像的基础上开发了一个轻量版Bitwarden_rs,经过我几年的使用,功能齐全,性能稳定,我想也是时候写一篇文章,寄希望于可以帮助更多的人在自己的服务器上保存自己的密码。

废话不多说,开整。

安装Docker

我在这里默认大家已经安装好了Docker以及Docker Compose,不知道如何安装的朋友可以参考官方文档如下。

Traefik 2的配置

接下来就是Traefik的Docker Compose文件,Traefik的静态和动态配置文件范例。在本文中我会把Traefik和BitWarden的docker-compose文件分开,这样两个conatiner不需要在同一时间生成以便于管理。同时我也放弃了外联gist,因为有些Hexo的皮肤对于外链gist的支持不好。

Docker Compose - docker-compose.yml

这一次我会详细讲解一下这个文件的一些配置和作用(我的粗浅理解,见笑见笑),以后其他文章可以直接引用。

Attributes Name Information
Image traefik:latest 这里我用的是最新的Traefik的docker image
container_name traefik 我把这个container命名为traefik方便之后的查看
security_opt no-new-privileges:true 防止container获取不必要的权限,可加可不加
ports 80:80 打开80端口(http)
443:443 打开443端口(https)
volumes /etc/localtime:/etc/localtime:ro 解决可能会出现的conatiner和主机时间不一致的问题
/etc/timezone:/etc/timezone:ro 解决可能会出现的conatiner和主机时间不一致的问题
/var/run/docker.sock:/var/run/docker.sock:ro 检测Docker的动向
./data/traefik.yml:/traefik.yml:ro 挂载本地静态配置文件traefik.yml
./data/acme.json:/acme.json 挂载acme.json文件用来放SSL证书
./data/configurations:/configurations 挂载configuration文件夹,里面是动态配置文件
networks proxy 把Traefik加入到外部的叫proxy的网络中
labels traefik.enable=true 允许Traefik使用本container
traefik.http.routers.traefik.entrypoints=http 定义通过哪个entry point可以访问。我在静态配置里面定义了http是80端口
traefik.http.routers.traefik.rule=Host(traefik.yourdomain) 定义访问http的host name
traefik.http.routers.traefik.middlewares=https-redirect@file 使用这个叫做https-redirect的middleware来把http转到https,该middleware我们会在动态配置文件里面定义
traefik.http.routers.traefik-secure.entrypoints=https 定义通过哪个entry point可以访问。我在静态配置里面定义了https是443端口
traefik.http.routers.traefik-secure.rule=Host(traefik.yourdomain) 定义访问https的host name
traefik.http.routers.traefik-secure.middlewares=user-auth@file 使用这个叫做user-auth的middleware,该middleware提供简易的认证服务,保护Traefik的dashboard。该middleware我们会在动态配置文件里面定义
traefik.http.routers.traefik-secure.tls=true 这里声明的是我们会使用SSL证书
traefik.http.routers.traefik-secure.tls.certresolver=letsencrypt 定义用具体的某个服务平台来提供证书,证书的平台在静态配置里面配置。
traefik.http.routers.traefik-secure.service=api@internal 这里是把api https的访问和traefik的dashboard作为服务连接在一起
networks: Proxy: external: true 定义Proxy这个网络
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
version: '3.3'

services:
traefik:
image: traefik:latest
container_name: traefik
restart: always
security_opt:
- no-new-privileges:true
ports:
- 80:80
- 443:443
volumes:
- /etc/localtime:/etc/localtime:ro
- /etc/timezone:/etc/timezone:ro
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./data/traefik.yml:/traefik.yml:ro
- ./data/acme.json:/acme.json
# Add folder with dynamic configuration yml
- ./data/configurations:/configurations
networks:
- proxy
labels:
- "traefik.enable=true"
- "traefik.http.routers.traefik.entrypoints=http"
- "traefik.http.routers.traefik.rule=Host(`traefik.yourdomain`)"
# Entry Point for https
- "traefik.http.routers.traefik.middlewares=https-redirect@file"
- "traefik.http.routers.traefik-secure.entrypoints=https"
- "traefik.http.routers.traefik-secure.rule=Host(`traefik.yourdomain`)"
- "traefik.http.routers.traefik-secure.middlewares=user-auth@file"
# ACME Certificate configuration
- "traefik.http.routers.traefik-secure.tls=true"
- "traefik.http.routers.traefik-secure.tls.certresolver=letsencrypt"
- "traefik.http.routers.traefik-secure.service=api@internal"

networks:
proxy:
external: true

看起来好像很复杂,实际上如果你用我的配置,只需要修改您的域名应该可以拿来就用……

Static Configuration - traefik.yml

这个我觉得还是挺好理解的。这里声明Traefik使用docker和file两个provider,docker当然不用解释,file这个provider主要挂载了动态的配置文件。

另外我添加了两个证书服务,一个是Let’sEncrypt,另外一个是BuyPass。两者都是免费的,前者的证书有效期3个月但是可以随意搭配包括通配符在内的所有DV证书。后者有效期6个月,但是只提供免费的单域名证书。对于普通用户来说,只要不需要通配符以及不需要在同一张证书上放所有的域名,用哪个都一样。对了,BuyPass有证书数量的限制,具体可以参考其官网

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
api:
dashboard: true

entryPoints:
http:
address: ":80"
https:
address: ":443"

providers:
docker:
endpoint: "unix:///var/run/docker.sock"
exposedByDefault: false
file:
filename: /configurations/dynamic.yml

certificatesResolvers:
letsencrypt:
acme:
email: [email protected]
storage: acme.json
keyType: EC384
httpChallenge:
entryPoint: http

buypass:
acme:
email: [email protected]
storage: acme.json
caServer: https://api.buypass.com/acme/directory
keyType: EC256
httpChallenge:
entryPoint: http

Dynamic Configuration - dynamic.yml

动态配置文件主要放的是一些middleware和SSL的选项。这些middleware还算是比较简单明了

Attributes Name Information
middleware https-redirect 这个用于http转接https
secureHeaders 这个用于,呃,把网站的ssllabs评级调到A+ ……
bw-stripPrefix BitWarden WebSocket需要,可选用
user-auth Traefik Dashboard,用户名和密码的基础认证
tls cipherSuites 规定使用的cipher suites,关闭一些比较弱的cipher。
minVersion 规定最低的TLS版本,我这里规定最低TLS1.2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# Dynamic configuration
http:
middlewares:
https-redirect:
redirectScheme:
scheme: https
permanent: true

secureHeaders:
headers:
frameDeny: true
sslRedirect: true
browserXssFilter: true
contentTypeNosniff: true
forceSTSHeader: true
stsIncludeSubdomains: true
stsPreload: true
stsSeconds: 31536000

bw-stripPrefix:
stripPrefix:
prefixes:
- "/notifications/hub"
forceSlash: false

user-auth:
basicAuth:
users:
- "admin:$apr1$tm53ra6x$FntXd6jcvxYM/YH0P2hcc1"

tls:
options:
default:
cipherSuites:
- TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
- TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
- TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
- TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
- TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305
- TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305
minVersion: VersionTLS12

BitWarden的Docker Compose - docker-compose.yml

这个docker compose文件跟上面Traefik的大同小异,只是多了websocket的部分。

老实说我自己正在用的BitWarden并没有用WebSocket,不过大部分教程包括官方实例都有包括它,所以我在大量试错之后找到了以下的配置方式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
version: '3'

services:
bitwarden:
image: bitwardenrs/server:latest
container_name: bitwarden
restart: always
volumes:
- ./bw-data:/data
environment:
- SIGNUPS_ALLOWED=false
- WEBSOCKET_ENABLED=true
networks:
- proxy
labels:
- "traefik.enable=true"
# Entry point for http
- "traefik.http.routers.bitwarden.entrypoints=http"
- "traefik.http.routers.bitwarden.rule=Host(`bw.yourdomain`)"
- "traefik.http.routers.bitwarden.service=bitwarden"
- "traefik.http.services.bitwarden.loadbalancer.server.port=80"
# Entry Point for https
- "traefik.http.routers.bitwarden.middlewares=https-redirect@file"
- "traefik.http.routers.bitwarden-secure.middlewares=secureHeaders@file"
- "traefik.http.routers.bitwarden-secure.entrypoints=https"
- "traefik.http.routers.bitwarden-secure.rule=Host(`bw.yourdomain`)"
- "traefik.http.routers.bitwarden-secure.service=bitwarden-secure"
- "traefik.http.services.bitwarden-secure.loadbalancer.server.port=80"
# Enable TLS
- "traefik.http.routers.bitwarden-secure.tls=true"
- "traefik.http.routers.bitwarden-secure.tls.certresolver=letsencrypt"
# websocket
- "traefik.http.routers.bitwarden-ws.entrypoints=https"
- "traefik.http.routers.bitwarden-ws.rule=Host(`bw.sslfor.fun`) && Path(`/notifications/hub`)"
- "traefik.http.middlewares.bitwarden-ws=bw-stripPrefix@file"
- "traefik.http.middlewares.bitwarden-ws=secureHeaders@file"
- "traefik.http.routers.bitwarden-ws.tls=true"
- "traefik.http.routers.bitwarden-ws.tls.certresolver=letsencrypt"
- "traefik.http.routers.bitwarden-ws.service=bitwarden-ws"
- "traefik.http.services.bitwarden-ws.loadbalancer.server.port=3012"

networks:
proxy:
external: true

最后

在修改保存好这些文件之后只需先输入docker network create proxy 生成proxy网络,然后分别在两个文件夹运行docker-compose up -d即可部署。

需要注意一点的是这两个文件需要放在不同的文件夹里面,比如说Traefik的这个放在~/docker-compose.yml,BitWarden这个可以放在比如说~/bitwarden/docker-compose.yml这样的文件夹下。

那么有没有办法不分开两个文件呢?有的😁

Traefik和BitWarden同一时间部署

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
version: '3'

services:
traefik:
image: traefik:latest
container_name: traefik
restart: unless-stopped
security_opt:
- no-new-privileges:true
ports:
- 80:80
- 443:443
volumes:
- /etc/localtime:/etc/localtime:ro
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./data/traefik.yml:/traefik.yml:ro
- ./data/acme.json:/acme.json
# Add folder with dynamic configuration yml
- ./data/configurations:/configurations
labels:
- "traefik.enable=true"
- "traefik.http.routers.traefik.entrypoints=http"
- "traefik.http.routers.traefik.rule=Host(`traefik.yourdomain`)"
# Entry Point for https
- "traefik.http.routers.traefik.middlewares=https-redirect@file"
- "traefik.http.routers.traefik-secure.entrypoints=https"
- "traefik.http.routers.traefik-secure.rule=Host(`traefik.yourdomain`)"
- "traefik.http.routers.traefik-secure.middlewares=user-auth@file"
- "traefik.http.routers.traefik-secure.tls=true"
- "traefik.http.routers.traefik-secure.tls.certresolver=letsencrypt"
- "traefik.http.routers.traefik-secure.service=api@internal"

bitwarden:
image: bitwardenrs/server:latest
container_name: bitwarden
restart: always
volumes:
- ./bw-data:/data
environment:
- SIGNUPS_ALLOWED=false
- WEBSOCKET_ENABLED=true
labels:
- "traefik.enable=true"
# Entry point for http
- "traefik.http.routers.bitwarden.entrypoints=http"
- "traefik.http.routers.bitwarden.rule=Host(`bw.yourdomain`)"
- "traefik.http.routers.bitwarden.service=bitwarden"
- "traefik.http.services.bitwarden.loadbalancer.server.port=80"
# Entry Point for https
- "traefik.http.routers.bitwarden.middlewares=https-redirect@file"
- "traefik.http.routers.bitwarden-secure.middlewares=secureHeaders@file"
- "traefik.http.routers.bitwarden-secure.entrypoints=https"
- "traefik.http.routers.bitwarden-secure.rule=Host(`bw.yourdomain`)"
- "traefik.http.routers.bitwarden-secure.service=bitwarden-secure"
- "traefik.http.services.bitwarden-secure.loadbalancer.server.port=80"
# Enable TLS
- "traefik.http.routers.bitwarden-secure.tls=true"
- "traefik.http.routers.bitwarden-secure.tls.certresolver=letsencrypt"
# websocket
- "traefik.http.routers.bitwarden-ws.entrypoints=https"
- "traefik.http.routers.bitwarden-ws.rule=Host(`bw.yourdomain`) && Path(`/notifications/hub`)"
- "traefik.http.middlewares.bitwarden-ws=bw-stripPrefix@file"
- "traefik.http.middlewares.bitwarden-ws=secureHeaders@file"
- "traefik.http.routers.bitwarden-ws.tls=true"
- "traefik.http.routers.bitwarden-ws.tls.certresolver=letsencrypt"
- "traefik.http.routers.bitwarden-ws.service=bitwarden-ws"
- "traefik.http.services.bitwarden-ws.loadbalancer.server.port=3012"

希望此教程可以刺激各位开始使用密码管理软件,甚至在自己的服务器上管理自己的密码。

谢谢观看