[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"site":3,"post-wo-ba-compose-de-override-xie-cuo-zhan":59,"related-wo-ba-compose-de-override-xie-cuo-zhan":93},{"name":4,"url":5,"description":6,"bio":7,"avatar":8,"skills":9,"tools":16,"social":34,"hero":47,"heatmap":52,"icp":55,"police":56,"policeIcon":57,"shortName":58},"时过境迁技术笔记","https:\u002F\u002Fhsgjq.top","一个记录前端与 Node 实践的个人博客，内容围绕 Vue、Nuxt、NestJS 与工程化展开，附源码级的原理笔记。","一名热爱技术的全栈开发者，专注 TypeScript 与 Web 工程化，习惯把踩过的坑和思考记录在这里。","\u002Fuploads\u002Fc485c101-179e-48db-96d1-238693784c38.jpg",[10,11,12,13,14,15],"TypeScript","Vue 3","Nuxt","NestJS","Node.js","工程化",[17,23,28],{"items":18,"title":22},[19,20,21],"Cursor","Git","Chrome DevTools","开发工具",{"items":24,"title":27},[25,26],"Vite","npm","构建工具",{"items":29,"title":33},[30,31,32],"Docker","CI\u002FCD(GitHub Actions)","Vercel","部署运维",[35,39,43],{"href":36,"icon":37,"label":38},"https:\u002F\u002Fgithub.com\u002Fhsgjq","github","GitHub",{"href":40,"icon":41,"label":42},"2971861643@qq.com","mail","邮箱",{"href":44,"icon":45,"label":46},"\u002Frss.xml","rss","RSS",{"lines":48,"subtitle":51},[49,50],"人生若只如初见，","何事秋风悲画扇。","《木兰花·拟古决绝词柬友》",{"enabled":53,"platform":37,"username":54},true,"hsgjq","桂ICP备2024046985号","桂公网安备45011002000103号","\u002Fuploads\u002F72c67ef3-bc86-4b64-b0df-0aba71065050.png","时过境迁",{"slug":60,"title":61,"excerpt":62,"date":63,"updatedAt":64,"tags":65,"content":66,"series":67,"seriesIndex":68,"seriesNav":69,"prev":87,"next":90},"wo-ba-compose-de-override-xie-cuo-zhan","我把 compose 的 override 写错，站点停了二十分钟","ports 是追加不是替换，合并之后 80 端口撞了；而 up 失败前旧容器已经被换掉。配置检查命令的输出格式也骗了我一次。","2026-09-03","2026-10-19",[30],"先说背景：这台机器上宿主机的 nginx 占着 80 和 443，Docker 里的 gateway 必须让开，绑到 127.0.0.1:8081。仓库里的 docker-compose.yml 写的是 \"80:80\"，所以每次重新上传代码，都要跑一条 sed 把端口改回来。\n\n我想免掉这条重复的 sed，于是建了 docker-compose.override.yml：\n\n```yaml\nservices:\n  gateway:\n    ports:\n      - \"127.0.0.1:8081:80\"\n```\n\n然后 `docker compose up -d gateway`。\n\n站点直接 502。\n\n原因：ports 是列表，compose 合并 override 时对列表是**追加**，不是替换。合并结果是两条都在：\n\n```yaml\nports:\n  - mode: ingress\n    target: 80\n    published: \"80\"\n    protocol: tcp\n  - mode: ingress\n    host_ip: 127.0.0.1\n    target: 80\n    published: \"8081\"\n    protocol: tcp\n```\n\n80 已经被宿主机 nginx 占着，绑定失败，容器起不来。而 compose 的执行顺序是\"先创建替换后的容器\"——旧的那个能服务的容器已经被换掉了。也就是说，失败发生在我已经失去服务之后。这一点是我最没预料到的：我以为 up 失败会保持现状。\n\n更糟的是我原本设了一个检查：\n\n```bash\ndocker compose config | grep '80:80'\n```\n\n没输出，我就判断\"合并成功、80 已经不在了\"。这个检查从头到尾没有效力——config 的输出是结构化字段（published \u002F target \u002F host_ip），根本不是 \"80:80\" 这个字符串形式。我 grep 的是一个不存在于输出格式里的写法。\n\n恢复过程里我又犯了第二个错。我把两条命令用 && 串起来：\n\n```bash\ngrep -q '80:80' docker-compose.yml && docker compose up -d gateway\n```\n\n前一条失败（正常，因为文件里是 127.0.0.1:8081），后一条根本没执行，而我盯着屏幕等了两分钟才反应过来。站点多停了一段。\n\n最后救回来的是最朴素的三步，一条一条执行：\n\n```bash\nrm docker-compose.override.yml\nsed -i 's|\"80:80\"|\"127.0.0.1:8081:80\"|' docker-compose.yml\ndocker compose up -d gateway\ndocker compose ps\n```\n\n之后我试过 ports: !override 这个标签，想让 override 文件替换而不是追加。在那台 compose 2.40.3 上，docker compose config 用这个标签之后一行输出都没有（报错走 stderr，被我管道吞了）。没验证成功的东西我不再往生产上带。\n\n最终方案是把端口变成一个显式变量，不依赖任何合并行为：\n\n```yaml\nports:\n  - \"${GATEWAY_PUBLISH:-80:80}\"\n```\n\n值写在 .env 里，重新上传代码不会覆盖它。默认值 80:80 保留，独立部署的人不受影响。\n\n这次之后我改了三条习惯：\n\n一、动线上容器的命令，一条一条跑，不用 && 串。串起来的代价是失败会静默跳过后面那步，而你以为它在跑。\n二、验证要看结构化输出，不要 grep 一个你想象中的字符串。现在我会明确看 published 和 host_ip 两个字段的值。\n三、任何\"让 compose 换掉正在跑的容器\"的操作，都要先假设它可能起不来。所以要么在低峰做，要么先确认回滚路径（旧镜像还在不在、能不能一条命令换回去）。","线上事故记录",2,[70,74,75,79,83],{"slug":71,"title":72,"seriesIndex":73},"1-9g-nei-cun-de-fu-wu-qi","1.9G 内存的服务器上，nuxt build 在第 36 秒被杀",1,{"slug":60,"title":61,"seriesIndex":68},{"slug":76,"title":77,"seriesIndex":78},"jian-li-de-xie-gang-hui-bian-cheng","键里的斜杠会变成冒号：清缓存清了个空",3,{"slug":80,"title":81,"seriesIndex":82},"dian-xiang-mu-ye-mian-jiu-si-yi","点「项目」页面就死：一次客户端无限重定向",4,{"slug":84,"title":85,"seriesIndex":86},"gang-deng-lu-jiu-guo-qi-yi-ci","一个 401，查出四个前端机制：缓存回放、隐藏页的 rAF、百分比圆角、缓动过冲",5,{"slug":88,"title":89},"swap-dui-shang-xian-he-gou-jian-nei","swap、堆上限和构建内存墙",{"slug":91,"title":92},"301-302-307-wo-yi-kai-shi-fen","301、302、307 我一开始分不清",[94,100,104,108,116],{"slug":95,"title":96,"excerpt":97,"date":98,"tags":99},"cong-da-bao-shang-chuan-dao-push-ji","从打包上传到 push 即上线：把 CD 接通","私有仓库的镜像包默认也是私有的，健康检查的探测地址在宿主机有 nginx 时会打错地方。两处都是配好之后才验出来的。","2026-10-02",[30,15],{"slug":88,"title":89,"excerpt":101,"date":102,"tags":103},"swap 让构建不被杀但变慢，抬堆上限让 Node 敢用更多内存，两件事要一起做。试过的几个数字和哪个最后成了，都写在里面。","2026-08-20",[30],{"slug":71,"title":72,"excerpt":105,"date":106,"tags":107},"V8 堆耗尽直接 SIGABRT，日志里只有一句低内存提示。先分清是被系统杀的还是自己崩的，再决定加内存还是加 swap。","2026-08-08",[30],{"slug":84,"title":85,"excerpt":109,"date":110,"tags":111},"响应头里的 token 被 304 原样回放，把刚签发的新 token 顶掉，表现成刚登录就过期。顺带记三个前端细节：隐藏标签页里 rAF 不执行、扁盒子的 50% 圆角是胶囊、缓动曲线的过冲只有 0.0008。","2026-10-13",[112,113,114,115],"CSS","JavaScript","Vue","浏览器",{"slug":80,"title":81,"excerpt":117,"date":98,"tags":118},"为了保留旧地址大小写加的一条跳转规则，被注册成了客户端路由；路由匹配不区分大小写，于是它自己跳自己。",[12,115]]