独立开发者的踩坑实录:部署灾难与那些差点毁掉项目的 Bug

做 SayCraft 的第一个月,踩的最大的坑不是代码写不出来——是部署和运维。这些故事没有技术含量,但每一个都让我印象深刻到现在写起来还心有余悸。


rsync 灾难:一条命令删光了服务器

5 月 25 号,我在调试 API 的时候 cd apps/api 进了子目录。调完之后顺手跑了部署脚本 bash deploy-prod.sh

部署脚本内部用的是 rsync .——同步"当前目录"到服务器,带 --delete。正常情况下,当前目录是项目根目录,rsync 会把整个项目推上去。但我在子目录里跑的,所以它只推了 apps/api/ 这一个子目录,然后——把服务器上所有其他文件全删了。

apps/webpackages/*docker/,全没了。

还好本地代码完整,从根目录重新跑一次就恢复了。数据库在 Docker volume 里没受影响。但这个"两分钟之内服务器变成空壳"的体验,直接让我在项目文档里加了醒目的 CRITICAL 警告:永远从项目根目录跑部署脚本,跑之前先 pwd 确认

Postgres 密码锁定:最折磨人的 Bug

这是整个项目里最恶心的一个 bug,反复发作了三次,横跨 6 月 8 号到 6 月 15 号。

症状很统一:/health 接口返回 200(它不查数据库),但所有业务接口全部 500。

根因是 Postgres Docker 的一个设计:密码只在第一次初始化时写入数据卷,之后改环境变量对已有的卷没用。如果 .env 里的 PG_PASSWORD 被改过(从 md5 哈希变成明文,或者反过来),API 容器读到的新密码就跟数据卷里冻结的旧密码对不上——然后所有数据库连接都报 28P01 password authentication failed

最坑的是诊断陷阱。Postgres 的 pg_hba.conf 里,127.0.0.1 设的是 trust(免密码),所以在容器内用 psql -h 127.0.0.1 测试,永远成功。你以为密码没问题,其实只是因为你绕过了密码校验。必须通过 Docker 网络主机名 postgres(走 scram-sha-256 认证)才能暴露真相。

这个陷阱让我的诊断多走了至少三次弯路。

修了三轮:

  • 第一轮:在部署脚本里加自愈逻辑——但代码没提交到 git,下次 clean checkout 部署又丢了
  • 第二轮:部署脚本里加 ALTER USER + 密码校验门控——但只在"走脚本"时生效,宝塔面板重启或手动 docker compose up 不走脚本,问题又复现
  • 第三轮(终结):把 ALTER USER 写进 docker-compose 的 entrypoint,每次 Postgres 启动都自动对齐密码,healthcheck 也改走 scram 认证路径

第三轮之后再没复发过。

修 bug 不怕根因复杂,怕的是修复措施本身有生效条件——"只要走官方路径就没事"这种假设迟早被绕过。最终的修复必须是无条件的。

7 轮 Code Review 没查出来的灾难

5 月 28 号的故事,我在上一篇里提过。这里补充一下更深的教训。

那次 review 修了十几个问题:路径穿越防御、CORS 白名单、连接池调参、索引优化……每一个都是实打实的改进。问题出在第 4 轮里加的一个"改进":给所有接口加了 owner check(通过 HTTP header 传 user ID)。

这个改动的逻辑完全正确——接口确实应该校验调用者身份。但实现路径有个死角:浏览器 WebSocket 协议不支持自定义 header。前端代码也压根没传这个 header。

7 轮 review 全用 curl -H 'x-user-id: ...' 做 smoke test。curl 可以随便加 header,所以测试全过。但真实用户用的是浏览器 WebSocket,没有这个 header,一上来就炸。

两个 AI review agent 也说"没问题"——它们只读代码不跑浏览器,看到"有 header 就校验、没有就拒绝"的逻辑是对的,但不知道 WebSocket 协议层面传不了这个 header。

教训:修安全的时候,最容易引入可用性灾难。加权限校验之前,先跑一遍真实客户端确认凭证传递通路是通的,再动手。

部署到错误的服务器

SayCraft 有两个部署环境:us 分支部署到海外服务器(saycraft.ai),china 分支部署到国内服务器。两个部署脚本名字不同:deploy-prod-us.shdeploy-prod.sh

6 月 5 号,我在 us 分支上手滑跑了 deploy-prod.sh(国内脚本),结果把海外分支的代码推到了国内服务器。两个环境的配置不一样(域名、Stripe 密钥、API 地址),混着用直接起不来。

又一条写进 CRITICAL 文档的规则:跑部署脚本前确认当前分支

跨境回源 502

同样是 6 月 5 号。海外站的预览页面全部 502。

排查发现:海外服务器在硅谷,但 nginx 反代到上海的对象存储桶取静态资源。单个请求延迟 ~700ms,预览页同时加载 9 个 iframe——跨太平洋的延迟直接叠爆,全部超时。

修复很直接:在硅谷同区新建存储桶,迁移了 4667 个文件,切 nginx 上游。延迟从 700ms 降到 80ms。然后又把源码(23,007 个)和会议(298 个)也搬了。一整天的体力活。

Docker 静默回退

还有一个隐蔽的坑:Docker BuildKit 构建失败时,部署脚本仍然打印成功。Compose 会默默用上一次的旧镜像重新创建容器。

你以为部署成功了,代码是最新的,但实际跑的是旧版本。测试当然通过——因为测的就是旧代码。

后来加了一条规矩:部署后必须验证实际效果(grep bundle 内容或检查数据库变更),不能只看脚本输出


总结

回看这些故事,没有一个是"技术能力不行"的问题。每一个都是流程和假设出了错:

  • 假设"当前目录一定是项目根" → rsync 删光服务器
  • 假设"改了环境变量密码就变了" → Postgres 密码锁定
  • 假设"curl 测试 = 浏览器测试" → WebSocket 翻车
  • 假设"脚本名字我分得清" → 部署到错误服务器
  • 假设"部署脚本说成功就是成功" → Docker 静默回退

一个人做 SaaS,没有 ops 团队兜底,每一个"不会吧"的假设都可能变成生产事故。你得学会不信任自己的记忆、不信任脚本的输出、不信任测试的覆盖度——然后为每一种不信任写一条自动化防线。

产品在这:saycraft.ai

感谢您的收看 祝你天天开心~
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇