生产事故复盘:从 rsync 绕路到 Docker 端口裸奔
换了一台电脑,部署崩了。页面白屏,样式全毁,/_next/static/ 全线 404。 表面是 nginx 配置问题,实际是一次从部署流程到安全架构的全面溃败。
事故时间线
- 阶段一:rsync 绕路 — 为了快速部署,我用 rsync 直接把文件推到服务器,绕过了 git。服务器跑起来了,但 git 仓库里的代码是旧的。
- 阶段二:换电脑崩溃 — 换了一台电脑,从 git 拉代码重新部署。git 里存的是有 bug 的旧版 nginx.conf(alias 指向不存在的目录),服务器上 rsync 推的正确版本被覆盖了。
- 阶段三:端口全裸奔 — 排查过程中发现 web(3000)、api(3001)、db(5432) 三个容器全部绑了 0.0.0.0,PostgreSQL 5432 端口对公网完全开放。
- 阶段四:nginx alias 炸弹 — git 里的 nginx.conf 把 /_next/static/ 指向 /var/www/next-static/,这个目录根本不存在。所有 JS/CSS 全线 404。
根因一:rsync 绕过 git
这是最深的根因。rsync 能快速同步文件,但它不经过版本控制。 服务器上跑的是 rsync 推的正确版本,git 里存的是有 bug 的旧版本。 换电脑后 git pull 回来的是旧版本,一行 git 就把所有修复覆盖了。
铁律:所有代码变更必须通过 git push → 服务器 git pull → docker compose build 流程部署。禁止 rsync 绕路。
根因二:Docker 端口全暴露
docker ps 显示三个容器的端口全部绑在 0.0.0.0 上:
services:
web:
ports:
- "3000:3000" # 绑了 0.0.0.0,全世界能扫到
api:
ports:
- "3001:3001" # 同上
db:
ports:
- "5432:5432" # PostgreSQL 裸奔公网!这意味着全世界都能 nmap 扫到你的 PostgreSQL。 不需要密码爆破,端口本身就是信息泄露——攻击者知道你跑了数据库。
修复:Docker internal 网络隔离,只有 nginx 对外。
services:
web:
networks:
- internal # 不映射端口,只内网
api:
networks:
- internal
db:
networks:
- internal
nginx:
ports:
- "80:80" # 唯一公网入口
- "443:443"
networks:
- internal
networks:
internal: # Docker 内部网络隔离修复后 docker ps 只显示 nginx 的 80/443, web/api/db 的端口在容器网络内部通信,外部完全不可达。
根因三:nginx alias 炸弹
nginx.conf 把 /_next/static/ 用 alias 指向了一个不存在的目录。 结果所有 JS/CSS 请求返回 404,页面白屏。
# 炸弹:指向不存在的目录
location /_next/static/ {
alias /var/www/next-static/; # 404!
}修复:改用 proxy_pass 代理到 Next.js standalone。
# 修复:代理到 Next.js standalone
location /_next/static/ {
proxy_pass http://web;
proxy_http_version 1.1;
proxy_set_header Host $host;
add_header Cache-Control "public, max-age=31536000, immutable" always;
}根因四:镜像臃肿
旧镜像 778MB,因为它打包了完整的 node_modules。 Next.js standalone 模式只打包运行时必需的文件。
# Production — standalone 模式
FROM node:20-alpine AS runner
WORKDIR /app
# standalone 服务端
COPY --from=builder /app/apps/web/.next/standalone ./
# 静态资源(必须手动 COPY)
COPY --from=builder /app/apps/web/.next/static ./apps/web/.next/static
# public 目录
COPY --from=builder /app/apps/web/public ./apps/web/public
WORKDIR /app/apps/web
CMD ["node", "server.js"]| 模式 | 镜像大小 |
|---|---|
| 非 standalone(打包 node_modules) | 778 MB |
| standalone(运行时精简) | 186 MB |
减重 76%。standalone 的核心思路:server.js + .next/static + public, 三件套就是全部运行时依赖。不需要 node_modules,不需要 devDependencies。
隐患清单(已修复)
| 隐患 | 风险 | 修复 |
|---|---|---|
| rsync 绕过 git | 换电脑后覆盖修复 | 只准 git push → pull → build |
| 3000/3001/5432 裸奔 | 公网可扫描数据库端口 | Docker internal 网络隔离 |
| nginx alias 炸弹 | 全线 404 白屏 | 改用 proxy_pass |
| 镜像 778MB | 启动慢、传输慢 | standalone 186MB(-76%) |
| /stats 无密码 | 流量数据泄露 | nginx Basic Auth |
教训
每一个环节都不是高深的技术问题——rsync 绕路、端口暴露、alias 指空、镜像臃肿——但叠在一起就构成了生产级灾难。 安全的本质不是加多少层防护,而是确保每一层的地基是稳的。
最深的一条:部署流程本身就是安全防线。 rsync 绕过了 git 这个版本控制的防线,让后续一切修复都变成了沙滩上的城堡。 换电脑只是触发器,真正的裂缝早就存在了。
在线体验:上面提到的 standalone 架构和 Docker 网络隔离,正在为 AudioWorklet 实时探针服务——
打开 Live Lab →2026-04-15 · 生产安全复盘 · diffserv.xyz