Article
Docker Compose 生产部署:端口、健康检查与宿主机 Nginx
Docker Compose 生产部署:端口、健康检查与宿主机 Nginx
很多教程会把「docker compose up -d 再映射 80/443」当成生产终局。单机单项目时偶尔能跑;同机已经有个人站、记账站、反代入口时,这样一定会抢端口、抢证书、互相污染配置。生产上更稳的方式是:容器只跑业务,宿主机 Nginx 统一做入口。这篇文章把理由、配置细节和发布回滚讲清楚,方便你按同一套约定继续加第三个、第四个站点。
为什么不要让容器直接占 80/443
容器一旦发布 80:80,就意味着它想成为整机流量入口。后续你要再加一个站点,就不得不改容器网络、做额外的反代层,或者把已有 Nginx 拆掉。运维成本会指数上升,排障时也分不清「是 compose 网络问题还是证书问题」。
更好的约束是:
- 业务容器只绑定
127.0.0.1:高位端口 - 宿主机 Nginx 负责域名、HTTPS、路径分流
- 证书存放在宿主机或明确挂载点,而不是散落在各个 compose 项目里
例如博客前端、后端可以是:
ports:
- "127.0.0.1:13000:3000"
- "127.0.0.1:18080:8080"
公网只打开 80/443,应用端口永远不进安全组。即使某个容器被打穿,攻击面也先被网关挡一层。绑定 127.0.0.1 比只写 13000:3000 更重要:后者默认可能听在 0.0.0.0,等于把应用端口暴露到公网,只是「碰巧云厂商防火墙挡住了」而已。
健康检查不是摆设
Compose 的 depends_on 如果不加条件,只保证「容器创建了」,不保证「服务可服务」。MySQL 启动后还有缓冲池初始化;后端可能还在跑迁移;前端可能还在 listen。结果是一串 502,再靠人工重启碰运气。
建议至少三层:
- MySQL:
mysqladmin ping - Backend:
GET /health返回 200 - Frontend:打到真实对外路径(注意
basePath;挂在根路径时是/)
然后在依赖上写 condition: service_healthy。这样发布会慢几秒,但失败会变得可预期:卡在哪一层健康检查,就查哪一层日志。健康检查还要注意超时与重试次数——检查过严会导致正常抖动误杀;过松则又回到「假健康」。对前端尤其别只会 wget 到容器内端口却忽略了 basePath:曾经挂在 /blog 时,打 / 可能 404,却被你当成失败。
Nginx 反代必须带对的头
最少要传:
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;
缺 X-Forwarded-Proto 时,应用经常误判自己仍在 HTTP,从而生成错误的绝对链接,或在强制 HTTPS 场景下形成重定向回路。缺 Host 时,多站点虚拟主机更容易串。Next.js 升级相关头有时也需要 Upgrade / Connection,建议一起加上,成本很低。
上传接口还要同步调大 client_max_body_size。只改应用、不改 Nginx,是最常见的「本地通、线上挂」原因之一。表现通常是浏览器 Network 里看到 413,或连接被直接重置;如果你只看应用日志,会觉得「请求根本没进来」。
与同机其他项目共存
同机常见形态是:IP 默认站点给某个服务,域名站点给另一个服务。Nginx 用 server_name 分流,而不是靠「谁先启动谁占用 80」。博客可以独占 lostyouth.cn / www,记账系统独占 family-ledger.lostyouth.cn,监控独占 stats.xxx。彼此 upstream 指向不同本机端口即可。
子路径挂载(/blog)适合临时避让,但会带来 basePath、资源路径、跳转闭环等额外复杂度。域名齐全后,优先让博客走根路径,子路径留给不得不共存的过渡阶段。从子路径切到根路径时,记得同步改前端构建参数、反代 location,以及外部书签——这是一次有意识的「破坏性 URL 变更」,不是只改 Nginx 那么简单。
多个 compose 项目也不要共用一个随意的 bridge 网络命名空间约定之外的神秘依赖。各自管理自己的网络与卷,跨项目只通过本机端口或明确声明的外部网络通信,排查时心理负担会小很多。
发布与回滚
生产发布可以拆成两步:同步代码,再在服务器执行 docker compose up -d --build。构建参数要随环境变:域名、协议、API 前缀。只改 Nginx 不重建前端,常常会看到页面已是 HTTPS,但接口还指向旧地址。
同步代码时把密钥排除干净:.env.prod、证书、私钥、本地调试配置都不该被 rsync 覆盖或上传。服务器上的状态文件要被视为「另一类真相来源」。上传目录与数据库卷要周期性备份,容器重建不该等于数据消失。
回滚策略要现实一点:镜像 tag 或至少保留上一次可用镜像;数据库迁移单独控制,避免「代码回滚了、表结构回不去」。如果这次发布包含不可逆迁移,回滚预案就应该写成「恢复备份」而不是「重新 compose down/up」。发布说明里用三行字写清:改了什么、怎么验证、失败时怎么退,已经强过大多数个人项目。
观察发布是否成功
不要只看 docker ps 全绿。按用户路径走一遍:打开首页、进详情、后台登录、上传一张小图、退出。再看 Nginx 与容器日志是否有新的 5xx。有统计系统的话,观察发布后几分钟的错误率;没有的话,至少用 curl -I 检查关键 URL 的状态码与证书是否仍有效。
小结
Compose 适合编排应用生命周期,不适合替代整个边缘入口。把端口收回到本机回环、把健康检查做实、把 Nginx 头字段与证书边界理清,同机多站点才能长期共存。等你开始加第四、第五个服务时,这套约定会比任何「一键脚本」都值钱——因为脚本会过期,约定可以继续长。