部署经验总结(Traefik + Docker Compose + 域名访问 + 共享中间件)
这篇文章总结了本仓库这次连续部署多个服务(dockhand、vaultwarden、Checkmate、Miniflux(rss))的经验、踩坑点、排查手法,以及我个人的部署习惯(也是本仓库后续建议遵循的“默认做法”)。
目标与底线
- 只允许域名访问:服务不对外暴露宿主机端口,避免
IP:端口绕过认证/暴露管理面。 - 统一入口:对外只开
80/443,由Traefik统一反代、签发证书、加安全头。 - 共享基础中间件:MongoDB / Redis / PostgreSQL 这类通用组件集中维护、统一复用,避免每个应用重复起一套浪费资源。
- Never break userspace(不破坏现有服务):所有变更优先考虑是否会影响已在线服务(路由冲突、端口冲突、网络冲突、认证方式冲突)。
目录组织与“我的部署习惯”
- 每个应用一个目录:例如
dockhand/、vaultwarden/、Checkmate/、v2/(Miniflux)。 - 每个应用目录下有一个可直接运行的
docker-compose.yml:避免依赖上游仓库示例分散在contrib/里不好维护。 - 共享中间件单独一个项目:新增
middlewares/,集中跑postgres/mongodb/redis。- 共享中间件只加入内部网络:对外不暴露任何端口。
- 应用通过
external network方式加入共享网络获取 DB/缓存能力。
- Traefik 统一网络:所有需要被 Traefik 访问的业务容器都加入
traefik_proxy外部网络。 - 不滥用文件路由:优先使用 Docker labels(docker provider)。file provider 只用于少数“非容器化目标”或特殊转发。
标准部署模板(核心套路)
1)禁止宿主机端口暴露
对外服务统一做法:
- 用
expose:(仅容器网络可见)替代ports:(宿主机可见)。 - 由 Traefik 通过 label 路由到容器端口。
这能一次性解决:
- 端口冲突(例如宿主机 3000 已被别的服务占用)
- IP:端口绕过域名(安全风险)
2)Traefik labels(域名路由 + TLS + 安全头)
常规标签集合(示意):
traefik.enable=truetraefik.http.routers.<name>.rule=Host(`miniflux.example.me`)traefik.http.routers.<name>.entrypoints=websecuretraefik.http.routers.<name>.tls.certresolver=letsencrypttraefik.http.routers.<name>.middlewares=security-headers@filetraefik.http.services.<name>.loadbalancer.server.port=<container_port>
3)多网络容器必须指定 Traefik 网络
当容器同时加入:
traefik_proxy(给 Traefik 用)infra_backend(给数据库/缓存用)
Traefik 可能选错 IP(选到它不在的网络),导致 504 Gateway Timeout。
解决:在该容器 labels 增加:
traefik.docker.network=traefik_proxy
本次 Checkmate 就是典型案例:加了这条后,504 立即消失。
共享中间件(middlewares 项目)
设计原则
- 高内聚低耦合:中间件只提供网络服务,不掺杂业务逻辑。
- 最小权限:默认不对外暴露端口,只在 Docker 内部网络给应用用。
- 数据持久化统一管理:卷在
middlewares项目里集中维护(便于备份/迁移)。
经验要点
- 共享网络使用固定名字(例如
infra_backend),各应用external: true加入。 - 应用连接字符串里使用共享网络的服务名(例如
postgres/mongodb)。 - 不同应用之间尽量使用不同的 DB / schema / user,避免互相污染数据。
应用级注意点(本次踩坑记录)
Dockhand(容器管理面,风险极高)
- 必须禁用宿主机端口映射,只允许域名 + Traefik 访问。
- 必须加认证:用 Traefik
basicAuth中间件保护。 - 不建议再叠加复杂逻辑,越简单越不容易出事故。
Vaultwarden(Bitwarden 兼容服务)
这是最容易“自作聪明搞坏客户端”的服务:
- 不要对整个站点加 BasicAuth:官方客户端/浏览器插件会直接不可用。
- 正确做法:仅保护
/admin(管理后台):/、/api、/identity等客户端路径必须无 BasicAuth 阻挡。
- 遇到“插件能打开但登录失败”,先看:
- Cloudflare 是否给 API 下发 challenge(见下文)
/api是否被客户端当作 ServerConfig(必要时在 Traefik 做精确重写到/api/config)
Checkmate(多网络 + Mongo)
- 共享 Mongo 后,容器加入多个网络会触发 Traefik 选错网络:
- 直接导致 504
- 用
traefik.docker.network=traefik_proxy一把解决
Miniflux(miniflux.example.me)
- Miniflux 只支持 PostgreSQL,不需要 Mongo/Redis(除非你额外跑 RSSHub、Browserless 等)。
HEAD /返回405是可接受的(GET 正常即可),不要把它误判成服务挂了。
Cloudflare 的坑:客户端/插件集体“无法登录”
这次出现过两次同源问题:
- Vaultwarden 浏览器插件:请求被 Cloudflare challenge 拦截 → 返回 403
- Reeder(Google Reader API):请求被 Cloudflare challenge 拦截 → 返回 403
共同特征:
- 响应头出现:
server: cloudflare+cf-mitigated: challenge - 请求根本到不了 Traefik/应用日志
解决策略(推荐优先级):
- A:该子域名切 DNS only(灰云):最稳,不需要客户端做任何配合。
- B:保留橙云,但对该 Host/路径跳过挑战:
- 至少放行:
/api、/identity、/reader/api/0、/accounts/ClientLogin等 API 路径
- 至少放行:
经验结论:
- “能在浏览器里打开 ≠ 客户端能用”:浏览器能做人机验证,客户端做不了。
排错手册(从快到慢)
1)先判定“请求到没到服务器”
- 如果 Cloudflare 403 challenge:请求没到服务器,先处理 Cloudflare/WAF。
- 如果 Traefik 404/504:请求到 Traefik,但路由/网络/后端不可达。
- 如果后端 4xx/5xx:请求到应用,查应用日志与配置。
2)Traefik 侧排查
- 看 access log:该 Host 的请求落到了哪个 router/service、返回码是多少。
- 多网络容器:优先怀疑
traefik.docker.network缺失。 - 同域名多路由来源:优先怀疑 file-provider 与 docker-provider 冲突(删掉旧 file 路由)。
3)应用侧排查
docker logs <container>看启动是否成功、是否监听端口、是否连上 DB。- 检查是否误用了
ports:导致端口冲突/暴露。
最终安全清单(上线前必做)
- 宿主机无业务端口暴露(只保留 80/443)
- 管理面必须有认证(dockhand、vaultwarden /admin 等)
- 数据库/缓存不对公网暴露
- Traefik 路由无冲突(同 Host 不重复定义 file-provider + docker-provider)
- Cloudflare 不对 API 下发 challenge(客户端/插件/第三方集成会挂)
Comments