36 Docker 化:打包 RAG 应用与基础设施
Docker 化:打包 RAG 应用与基础设施
Docker 化不是为了显得项目专业,而是为了让项目离开自己电脑后仍然能启动、迁移、排错和演示。
[TOC]
1. 这一节到底解决什么问题
前面我们已经把文档上传、解析、切片、Embedding、向量入库、向量检索和基础问答串起来了。到这里,RAG 已经不再只是一个“能回答”的 demo。
从 Day30 开始,重点要慢慢转向工程质量:
答案为什么可信
错误为什么能定位
效果为什么能比较
成本为什么能估算
部署为什么能复现
项目为什么能讲清楚
很多项目本地能跑,是因为你电脑上刚好装了数据库、环境变量、目录和依赖。Docker 化要把这些隐含前提写出来。
2. 先看一个业务场景
你把项目发给别人,对方没有 PostgreSQL,没有 pgvector,也不知道模型 Key 配在哪里。如果 README 只写“启动项目”,对方基本跑不起来。Compose 可以把应用、数据库、卷和环境变量组织成一套可复现环境。
这个场景里,最容易出错的地方通常不是某一行代码,而是链路边界没有想清楚。
输入是什么
依赖哪份资料
中间结果怎么保存
失败时返回什么
如何证明改动有效
带着这些问题往下看,后面的设计就不会变成空泛理论。
这一节要先说明为什么要 Docker 化。
不是为了显得“部署专业”,而是为了解决环境不可复现的问题。
判断顺序可以这样写:
1. 先列出项目运行依赖:Java 服务、Python 服务、PostgreSQL、pgvector、Redis、模型配置
2. 再判断哪些依赖适合容器化,哪些只需要环境变量配置
3. Compose 文件要表达服务关系、端口、数据卷、健康检查和启动顺序
4. 镜像里不要写死密钥,密钥通过环境变量或配置文件注入
5. 最后用干净环境执行启动命令,证明别人也能跑起来
它解决的是环境复现问题:代码在自己电脑能跑,但换一台机器就缺依赖、缺扩展、缺配置。
3. 核心概念
1. Dockerfile
负责把应用打成镜像。重点是固定基础镜像、多阶段构建、非 root 用户和健康检查。
可以把它记成一句话:镜像封装应用。
2. Docker Compose
负责组织多个服务,例如应用、PostgreSQL、Redis、Nginx。服务间通过服务名访问。
可以把它记成一句话:Compose 封装环境。
3. 数据卷
数据库数据必须放在 volume 中,容器重建不应丢数据。
可以把它记成一句话:数据不能跟容器生命绑定。
4. 配置注入
密钥和环境差异通过环境变量或 secret 注入,不写进镜像和仓库。
可以把它记成一句话:配置属于运行时。
4. 整体流程
graph TD;
A["构建应用镜像"] --> B["启动 PostgreSQL + pgvector"]; B["启动 PostgreSQL + pgvector"] --> C["执行数据库迁移"];
C["执行数据库迁移"] --> D["启动应用"];
D["启动应用"] --> E["健康检查"];
E["健康检查"] --> F["导入演示数据"];
F["导入演示数据"] --> G["运行问答冒烟"];
图里每个节点都应该能单独打日志、单独统计耗时、单独写测试。
这就是我一直强调的:不要把 RAG 写成一个黑盒方法。黑盒方法演示时很爽,排查时很痛苦。
5. 落地步骤
1. 列出依赖
先写清项目依赖哪些服务、端口、环境变量、目录和模型配置。
落地时要留下可检查的产物:依赖清单。
2. 编写 Dockerfile
使用多阶段构建,运行阶段只保留必要文件,避免镜像过大。
落地时要留下可检查的产物:应用镜像。
3. 编写 Compose
定义数据库、应用、网络、volume、健康检查和依赖关系。
落地时要留下可检查的产物:compose.yaml。
4. 从零验证
在干净环境按 README 启动,导入演示文档并完成一次问答。
落地时要留下可检查的产物:启动验证记录。
6. 数据结构或配置示例
| 项目 | 说明 |
|---|---|
| POSTGRES_DB | 数据库名 |
| POSTGRES_PASSWORD | 数据库密码,敏感 |
| SPRING_DATASOURCE_URL | 应用连接数据库地址 |
| MODEL_API_KEY | 模型服务密钥,敏感 |
| UPLOAD_DIR | 文件上传目录或对象存储配置 |
如果项目里已经有自己的字段命名,可以不照抄这张表。但这些信息最好都能找到对应位置,否则排查问题时会缺证据。
7. 示例
services:
postgres: image: pgvector/pgvector:pg17 environment: POSTGRES_DB: rag POSTGRES_USER: rag_user POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} volumes: - postgres_data:/var/lib/postgresql/data
app: image: rag-service:1.0.0 environment: SPRING_DATASOURCE_URL: jdbc:postgresql://postgres:5432/rag
volumes:
postgres_data:
示例的重点不是格式,而是思路:把规则、参数、输入、输出和失败状态显式写出来。
8. 为什么不能简单粗暴地做
不能只在 README 里写“先安装 PostgreSQL”。学习阶段可以手动装,但项目展示和交付阶段,最好把关键依赖容器化,减少环境差异。
很多初学者会希望有一个“最简单固定答案”。但 RAG 项目里,简单粗暴通常会把问题藏起来:短期好像能跑,长期很难维护。
9. 调试和排查路径
当结果不符合预期时,建议按这个顺序排查:
1. 输入问题是否正确
2. 权限范围是否正确
3. 检索候选是否包含正确证据
4. 排序或过滤是否把正确证据丢掉
5. Prompt 是否把证据清楚交给模型
6. 模型是否按证据回答
7. 引用是否能支持结论
8. 日志、成本、耗时是否异常
不要一看到答案不对就去改 Prompt。很多时候问题根本不在 Prompt,而是在解析、切片、过滤、召回或数据版本。
10. 工程取舍
- Compose 适合学习、演示和单机部署。
- 生产环境可以继续演进到云数据库或 Kubernetes。
- Docker 简化环境,但不会自动解决数据库迁移和密钥管理。
取舍要写在文档里。因为过几周再回来看,你很可能只记得“当时这么做了”,却忘了“为什么这么做”。
11. 常见坑
- 容器里还使用 localhost 连接数据库。
- 把 API Key 写进 Dockerfile。
- 数据库没有 volume。
- 容器启动了但没有跑真实问答冒烟。
这些坑不是为了吓人,而是为了让你知道项目坏掉时从哪里下手。
12. 验收清单
- ☐ 应用能打成镜像
- ☐ PostgreSQL + pgvector 可启动
- ☐ 数据库数据持久化
- ☐ 密钥不进入镜像
- ☐ 干净环境可完成一次问答
验收清单是博客里很有价值的部分。它能把一篇文章从“我讲了一个概念”变成“你可以跟着做出一个结果”。
13. 落地清单
- ☐ 写 .env.example
- ☐ 启动 pgvector 容器
- ☐ 让应用用服务名连接数据库
- ☐ 重启容器验证数据不丢
落地后,建议把结果截图或记录到项目文档里。后面写 README、博客复盘或项目说明时,这些都是现成素材。
14. 小结
Docker 化解决的是可复现问题。它让项目从“我电脑上能跑”变成“别人按步骤也能跑”。
到这里,你应该能把今天的主题讲成一个完整故事:为什么需要它、它解决哪一层问题、怎么落地、怎么验证、有什么代价。