Agent Wiki RAG 三件套公网部署实录Docker 化的 21 个坑第 8 个直接让我裂开关键词Agent 部署 · Docker 多阶段构建 · pgvector · zhparser 中文分词 · 数据迁移 · 韩国服务器 · Windows 环境前言背景交代一下我手上有个叫navigate的个人 Agent 项目技术栈是LangChain LangGraph带三块核心能力Agent—— 基于 LLM 的工具调用循环能操作文件、执行命令、检索知识Wiki—— 集成 zyplayer-doc 知识库Java MySQL做文档沉淀RAG—— PostgreSQL pgvector 向量检索配合 zhparser 中文分词实现混合检索向量 BM25 关键词兜底本地跑得好好的今年终于下定决心把它部署到公网。买了一台阿里云韩国节点轻量服务器2核2G79元/年免备案——对免备案真香然后就是长达两天的渡劫之旅。这篇文章把整个过程中踩过的坑全部复盘一遍按类别整理成 6 组 21 个坑。特别是 Windows 本地环境那组第 8 个坑我直接被整不会了。部署架构先看最终长什么样这是踩完所有坑之后的目标形态公网用户 ──HTTPS── Caddy 反向代理(:443) ── navigate-app(:3001, 仅内网) │ ├── postgres pgvector zhparser(:5432, 仅内网) └── zyplayer-doc(:8083, 可选)核心原则一切端口收进内网公网只留 80/443密钥全部走环境变量注入数据全部进 Docker volume。踩坑目录分组坑一句话镜像与构建1. 项目根本没有应用 Dockerfile只有个 pg 的基础镜像2. tsc 不复制静态资源登录页/简历页 404 的元凶3. .dockerignore 误伤 resume.mdCOPY resume.md报 not found4. zhparser 中文分词装不上Debian 没包只能源码编译5. 服务器 2G 内存构建 OOM必须加 swap6. 多阶段构建镜像从 1.5G 瘦到 400M安全7. 数据库密码是公开默认值navigate/navigate8. Express 默认 0.0.0.0裸奔公网必须收进内网9. 密钥打进镜像env_file 注入才对Windows10. PowerShell 跑 Linux 命令head/grep/cd /d 全是坑11. PowerShell 重定向损坏二进制pg_dump 导出一坨乱码12. Git Bash 的 MSYS 路径转换/backup变成了C:/Program Files/Git/backup访问13. 没有域名SSH 隧道先跑起来14. Caddy 自动 HTTPS必须要有域名 解析zyplayer15. 登录后跳回登录页SPA 的 localStorage 残留16. zyplayer 数据迁移MySQL dump 文件卷数据迁移17. RAG 数据在 pgpg_dump 直接搬向量不用重算18. SQLite 和上传文件navigate.db rag_uploads 卷迁移19. 迁移顺序先扩展、后数据、再起服务20. 个人镜像拉不到liuwenbo/pg_vector_fts 是个雷21. 服务器构建太慢一次 10-20 分钟靠 layer 缓存续命下面挑重点展开讲。第一组镜像与构建坑 1-6坑 2tsc 不复制静态资源登录页 404现象本地npm run build编译通过npm start也能跑但部署到 Docker 里一访问/login就是 404。原因项目的前端页面login.html、resume.html等放在src/server/public/而tsc只编译.ts文件根本不会把.html复制到dist/。本地开发用 tsx 直接跑源码所以没事一上生产就原形毕露。解决Dockerfile 里手动补一步# tsc 不复制静态资源必须手动补上否则登录页/简历页 404 COPY --frombuilder /app/src/server/public dist/server/public/教训本地能跑 ≠ 生产能跑。开发用的是 tsx源码直跑生产是编译产物两类环境差异要提前盘清楚。坑 3.dockerignore的*.md误伤resume.md现象构建跑到COPY resume.md ./报错failed to compute cache key: /resume.md: not found。原因为了精简构建上下文我在.dockerignore里写了*.md排除所有文档。结果resume.md简历数据应用运行时要读也被排除了。文件在磁盘上明明存在但被排除在构建上下文之外COPY 自然找不到。解决dockerignore 和 gitignore 一样后面的规则覆盖前面的用!重新包含*.md !resume.md !skills/*.md教训.dockerignore排除规则要先粗后细排除所有之后记得用!把运行期必需的文件捞回来。这是最容易阴沟翻船的一步。坑 4zhparser 中文分词装不上数据库起不来现象postgres 容器构建失败apt 报E: Unable to locate package libscws-dev。原因这个项目的数据库迁移脚本硬依赖zhparser中文全文检索分词插件创建chinese_zh文本搜索配置。但本地 compose 用的liuwenbo/pg_vector_fts:pg17是个人镜像服务器上大概率拉不到坑 20官方pgvector/pgvector:pg17镜像没有 zhparserDebian bookworm 仓库里没有libscws-dev包SCWS 是 zhparser 的底层分词库三层叠加直接把数据库堵死。解决写一个 fallback 构建脚本源码编译 SCWS → 再编译 zhparserFROM pgvector/pgvector:pg17 RUN apt-get update \ apt-get install -y --no-install-recommends \ build-essential postgresql-server-dev-17 git ca-certificates wget \ autoconf automake libtool pkg-config \ rm -rf /var/lib/apt/lists/* # SCWS 源码编译GitHub 源码包不含 configure用自带 acprep 生成 RUN wget -q https://github.com/hightman/scws/archive/refs/tags/1.2.3.tar.gz -O /tmp/scws.tar.gz \ mkdir -p /tmp/scws \ tar xzf /tmp/scws.tar.gz -C /tmp/scws --strip-components1 \ cd /tmp/scws \ ./acprep \ ./configure --prefix/usr/local \ make -j$(nproc) make install ldconfig \ rm -rf /tmp/scws /tmp/scws.tar.gz # zhparser 编译指定 PG17 的 pg_config 和 SCWS 路径 RUN git clone --depth 1 https://github.com/amutu/zhparser.git /tmp/zhparser \ cd /tmp/zhparser \ make PG_CONFIG/usr/lib/postgresql/17/bin/pg_config SCWS_HOME/usr/local \ make install PG_CONFIG/usr/lib/postgresql/17/bin/pg_config SCWS_HOME/usr/local \ rm -rf /tmp/zhparser教训CREATE EXTENSION这类硬依赖部署前就要在目标环境验证好。个人镜像、第三方扩展、官方镜像缺组件三座大山提前搬。坑 5服务器 2G 内存构建 OOM现象npm citsc编译过程中内存被打爆构建直接失败或卡死。解决服务器上先建 4G swapfallocate-l4G /swapfilechmod600/swapfilemkswap/swapfileswapon/swapfileecho/swapfile none swap sw 0 0/etc/fstab教训买服务器别只看价格内存决定你能不能构建。2G 内存跑 Node 编译必须配 swap这是部署 Agent/RAG 这类依赖重的项目的第一课。第二组安全坑 7-9坑 7数据库密码是公开默认值现象本地docker-compose.yml里POSTGRES_PASSWORD: navigate——这密码等于没有。解决生产编排文件里密码走环境变量部署时用docker compose --env-file .env.prod注入environment:POSTGRES_PASSWORD:${POSTGRES_PASSWORD}# 从 .env.prod 读不写死在文件里注意compose 的${}变量替换读的是--env-file指定的文件不是env_file:那是注入容器的这俩别搞混。坑 8Express 默认监听 0.0.0.0现象app.listen(port)没指定 host默认绑定所有网卡——公网裸奔。解决端口只绑本机公网流量全部走 Caddyports:-127.0.0.1:3001:3001# 只绑本机运维 SSH 内调试用postgres 干脆不映射端口只在 compose 内网可达。坑 9密钥千万别打进镜像.env.prod里有OPENAI_API_KEY、数据库密码、登录密码。做法是Dockerfile不 COPY 任何.env*运行配置用 compose 的env_file: .env.prod注入.env.prod加进.gitignore原 gitignore 居然没忽略它差点提交上去第三组Windows 本地环境的噩梦坑 10-12——最折磨人的一组这组坑全是因为本地是 Windows服务器是 Linux两个环境的 shell 行为差异导致的。每个都让我在凌晨对着报错发呆。坑 10PowerShell 跑 Linux 风格命令现象head : 无法将head项识别为 cmdlet、函数、脚本文件或可运行程序的名称tar.exe: Must specify one of -c, -r, -t, -u, -x原因我把 Linux 命令cd /d、head、grep、反斜杠续行直接粘到 PowerShell 里跑。PowerShell 里没有head/grepcd /d是错误语法\也不是续行符。解决分清环境——PowerShellhead→Select-Object -Firstgrep→Select-String路径D:\xxGit Bash原汁原味 Linux 命令路径/d/xx最佳实践VS Code 终端右上角下拉切到Git Bash之后本地全部用 bash和服务器一致少一半问题。坑 11PowerShell 重定向损坏二进制现象pg_dump -Fc navigate.dump导出的文件在服务器上pg_restore报格式错误mysqldump x.sql导入后中文全乱。原因PowerShell 的重定向默认按UTF-16 文本处理二进制流-Fc自定义格式 dump直接被写坏UTF-8 文本也会被转码。解决在容器内重定向再用docker cp把文件拷出来dockerexeczyplayer-mysqlsh-cmysqldump -uroot -p... --databases zyplayer_doc /tmp/zyplayer_doc.sqldockercpzyplayer-mysql:/tmp/zyplayer_doc.sql ./或者用 Git Bash字节流无此问题。教训Windows 上任何输出重定向到文件的操作涉及二进制或编码时都要警惕。容器内重定向 docker cp 是最稳的。坑 12Git Bash 的 MSYS 路径自动转换现象tar: cant open C:/Program Files/Git/backup/zyplayer-files.tar.gz: No such file or directory原因Git BashMSYS调用原生 Windows 程序时会自动把命令里以/开头的参数容器内路径/backup、/backup/xxx.tar.gz转换成 Windows 路径。于是容器内路径变成了C:/Program Files/Git/backup容器里当然找不到。解决命令前加MSYS_NO_PATHCONV1禁用转换MSYS_NO_PATHCONV1dockerrun--rm-vnavigate_zyplayer-files:/data-v$(pwd):/backup alpinetarczf /backup/x.tar.gz-C/data.或者干脆切到 PowerShell 执行 docker 命令PowerShell 没有 MSYS 转换。教训在 Windows 上任何带容器内路径的 docker 命令都是高危操作。要么统一 PowerShell要么统一 Git Bash MSYS_NO_PATHCONV1别混着来。第四组无域名的访问方案坑 13-14坑 13没有域名怎么办——SSH 隧道服务器没绑域名备案也不用备韩国节点公网暂时只给自用。最优雅的方案是 SSH 隧道把服务器端口映射到本地# 本地执行转发 app(3001) wiki 代理(3003)ssh-L3001:127.0.0.1:3001-L3003:127.0.0.1:3003 root服务器IP然后浏览器访问http://localhost:3001/login。隧道想常驻就加-N -f关闭用ssh -O exit。坑 14Caddy 自动 HTTPS 必须有域名Caddy 是最省心的反代——两行配置自动申请/续期 Let’s Encrypt 证书。但前提是有域名且 A 记录解析到服务器{$DOMAIN:localhost} { reverse_proxy app:3001 }没域名时 Caddy 只能内部跑先用 SSH 隧道顶着域名下来再切 HTTPS。第五组zyplayer 的坑坑 15-16坑 15改了密码登录后马上跳回登录页现象给 zyplayer-doc 重新设了密码重登后立刻被踢回登录页无限循环。原因zyplayer-doc 是前端 SPA登录态存在浏览器 localStorage不是 cookie。改密码后浏览器里残留旧的登录态 token → 打开页面带着旧 token 请求 → 401 → 跳登录重新登录写入新 token但残留数据没清干净循环往复另外如果走 navigate 的 wiki 反向代理访问代理内部还有会话缓存默认 TTL 600 秒改完密码必须重启 navigate 或等缓存过期。解决F12 → Application → Local Storage / Cookies → 找到 zyplayer 域 → Clear。或者直接无痕窗口测试能登录就是残留问题。坑 16zyplayer 数据迁移 MySQL dump 文件卷zyplayer-doc 的数据分两块MySQL 库zyplayer_doc文档/目录/权限文件卷zyplayer-files上传的附件。迁移时两个都要搬# 导出容器内重定向防编码坑dockerexeczyplayer-mysqlsh-cmysqldump -uroot -p... --databases zyplayer_doc /tmp/z.sqldockercpzyplayer-mysql:/tmp/z.sql ./# 文件卷打包dockerrun--rm-vnavigate_zyplayer-files:/data-v$(pwd):/backup alpinetarczf /backup/files.tar.gz-C/data.服务器恢复时顺序很重要先只起 MySQL → 导入数据 → 恢复文件卷 → 再起 zyplayer-doc避免它抢先初始化建表冲突。第六组数据迁移坑 17-21坑 17RAG 数据在 pg 里pg_dump 直接搬RAG 的文档、分块、向量全在 PostgreSQL。关键结论向量不用重算。只要两边 embedding 模型一致都是text-embedding-3-small、pg 版本一致都是 17、扩展一致pg_dump导出导入就是完整的# 本地Git Bashdockercomposeexec-Tpostgres pg_dump-Unavigate-dnavigate-Fcnavigate.dump# 服务器catnavigate.dump|dockercompose...exec-Tpostgres pg_restore-Unavigate-dnavigate--clean--if-exists坑 18-19SQLite、上传文件、迁移顺序除了 pg还有三个尾巴navigate.dbSQLite简历向量索引rag_uploads/上传的源文档不迁移的话删除/重建文档功能会找不到源文件会话数据也在 pg 里随 dump 一起走了迁移顺序铁律先让服务器把扩展建好init-pg.sql→ 导入数据 → 替换 SQLite/文件卷先停 app→ 再起服务 → 验证 count 一致。坑 20个人镜像拉不到liuwenbo/pg_vector_fts:pg17这种个人镜像本地能用可能很久以前 pull 过服务器上docker pull直接失败。生产环境所有镜像必须来自官方仓库或自己能构建这是我这次最深的体会之一。坑 21服务器构建慢一次完整构建 postgres 编译 zhparser5-15 分钟 app 的 npm ci tsc3-8 分钟。缓解手段Docker layer 缓存先build postgres成功后再 build app互不影响多阶段构建让 runner 层尽量小.dockerignore把node_modules/wiki-js4.7 万个文件排除build context 从几百 MB 压到几 MB总结给后来者的建议Windows 用户先统一终端环境Git Bash 一把梭记住MSYS_NO_PATHCONV1二进制导出用容器内重定向 docker cpDockerfile 从本地能跑推演生产怎么跑静态资源、相对路径、只读文件、数据卷逐项盘第三方扩展和镜像提前在目标环境验证zhparser 这类硬依赖别等到部署才炸安全三件套弱密码必改、端口收内网、密钥不进镜像数据迁移先列清单pg 库 / SQLite / 文件卷 / 源文档缺一个功能就残一个内存不够就 swap2G 机器构建 Node 项目必须做没有域名就用 SSH 隧道别为 HTTPS 硬着头皮等备案部署不是把代码跑起来而是把代码 数据 依赖 安全一起搬过去。希望这篇踩坑实录能帮你少走一半弯路。如果这篇文章对你有帮助欢迎点赞收藏。有任何部署问题欢迎评论区交流我会第一时间回复。