Python API Docker Packager
把“Python API 能运行”整理成可复现、可启动、可验收、可归档的镜像交付链路。以项目真实入口和真实依赖为准,不套用固定的 uvicorn main:app 模板。
核心原则
- 先读工程,再改打包文件。确认应用入口、监听地址、健康端点、依赖锁、原生库、运行目录和数据挂载。
- 区分两条链路:
start-dev负责构建、启动和健康验收;package-image负责版本递增、镜像导出和离线归档。 - Compose 配置可解析只是静态检查,不等于镜像构建成功、容器健康或挂载可写。
- 开发 Compose 可以含
build;生产 Compose 默认只消费指定镜像,避免在部署机隐式构建。 .env.production只放非敏感部署参数。密码、令牌和私钥使用 secrets、宿主机注入或外部配置。- 不凭报错直接修改业务代码。先区分应用/构建错误、Compose 配置错误与 Docker daemon/存储错误。
先建立文件依赖图
逐个检查下列文件;缺失时再按工程需要生成:
- 应用入口与配置:
main.py、包内app.py、健康端点、路径环境变量。 - Python 依赖:
pyproject.toml+uv.lock,或requirements*.txt。 - 原生依赖:系统包、源码版本、校验和、编译参数、补丁与模型运行资源。
- 镜像:
Dockerfile、.dockerignore、.gitattributes。 - 编排:
docker-compose.dev.yaml、docker-compose.yaml、.env.production。 - 入口脚本:
scripts/start-dev.ps1,必要时保持scripts/start-dev.sh等价。 - 发布脚本:
scripts/package-image.ps1/.sh、buildx/bake 配置和归档产物。 - 文档:README 中的实际命令、端口、镜像名、版本策略和挂载说明。
遇到含原生算法、源码补丁、离线镜像归档或 start-dev 统一入口的工程时,完整阅读 打包链路参考。
工作流
1. 识别真实运行契约
- 找到容器真正执行的模块或脚本,确认它是否自行调用 Uvicorn/Gunicorn。
- 确认容器内监听
0.0.0.0,并从代码或配置中取得真实内部端口。 - 找到轻量健康端点;现有
/足够稳定时可以复用,不强行改成/healthz。 - 记录输入目录、输出目录、日志文件、背景数据和模型可执行文件等运行时契约。
- 判断哪些路径只读、哪些必须可写,禁止把 Windows 宿主机路径传入容器内应用配置。
2. 固化依赖和原生构建
- 优先使用锁文件安装:uv 工程使用
uv sync --locked --no-dev;requirements 工程使用固定版本和哈希策略。 - 多阶段构建中将编译器、头文件和源码留在 builder;runtime 只复制运行库、可执行文件和应用必需文件。
- 固定上游源码 tag/commit、下载地址、SHA256 和关键构建参数。不要只依赖浮动分支或
latest。 - 在镜像构建中先执行补丁 dry check,再应用补丁。补丁必须以 LF 进入构建上下文,并用
.gitattributes固化*.patch text eol=lf。 - 对 Python 模块、原生动态库、算法可执行文件和必需资源增加构建期存在性/导入检查。
3. 生成或修正 Dockerfile
- 固定基础镜像版本,必要时固定 digest。
- 优化层缓存:稳定依赖文件先复制,频繁变化的业务源码后复制。
- 显式创建运行目录、复制必要资源并配置运行用户;非特权可行时使用非 root 用户。
CMD/ENTRYPOINT必须匹配第 1 步识别的真实入口。- 健康检查使用项目真实端点和内部端口,不依赖宿主机映射端口。
- 注入版本、构建时间和 VCS revision 等 OCI 元数据时,确保发布脚本与 Dockerfile 参数同名。
4. 对齐 Compose 与环境文件
docker-compose.dev.yaml包含build,承载本地构建、挂载、端口和健康检查。docker-compose.yaml默认只包含image,生产环境避免隐式构建;离线场景可设置合适的 pull 策略。.env.production提供镜像仓库/标签、容器名、宿主端口、宿主挂载目录和容器内 Linux 路径。- 启动脚本如果自动选择端口并回写环境文件,README 必须说明这一行为。
- 指定唯一发布版本源(通常为
pyproject.toml),并定义是否添加v、架构后缀等标签转换。比较锁文件、Dockerfile 版本参数、Compose 默认值、.env.production、归档脚本和 README 示例;发现未声明为部署覆盖的漂移时阻断发布。
5. 把 start-dev 作为统一验收入口
scripts/start-dev.ps1 至少应完成:
- 解析并导入指定 env 文件。
- 规范化 Windows 宿主路径,保留容器内 Linux 路径。
- 解析 Compose config,并断言目标服务存在、输入挂载只读、输出/日志挂载可写、挂载 target 与容器路径变量一致。
- 可选地检测端口占用;只有真实启动流程可以把最终端口回写到同一个 env 文件。
- 执行
docker compose up -d --build。 - 等待容器健康;失败时输出状态和容器日志并返回非零退出码。
- 明确打印最终镜像完整 tag/image ID、容器名、宿主端口和访问 URL。
先运行不构建、不启动容器的配置验收:
.\scripts\start-dev.ps1 -EnvFile .\.env.production -ConfigOnly
ConfigOnly 必须在 docker compose build/up 和任何 env 文件回写之前退出。若审查到现有脚本会在此模式下改写 APP_PORT 等配置,不得宣称它“无副作用”;应优先调整执行顺序,调整前要告知用户该写副作用。
最终打包/启动验收使用用户指定的统一入口;当 EnvFile 是首个位置参数时,下列两种写法等价:
.\scripts\start-dev.ps1 .\.env.production
.\scripts\start-dev.ps1 -EnvFile .\.env.production
生成的 README 和交付说明优先使用带 -EnvFile 的显式写法。真实执行会构建镜像、创建或更新容器并可能回写端口,执行前取得用户许可。
6. 单独处理发布归档
package-image与start-dev不互相冒充:前者产生可分发 TAR/ZIP,后者证明镜像在 Compose 契约下能健康运行。- 发布脚本应支持 DryRun,显示版本变化、镜像标签、构建参数和输出路径。
- 版本递增后同步锁文件、Dockerfile 构建参数和需要固化版本的部署文件。
- 构建后检查镜像架构、入口、关键环境变量和 OCI 标签,再导出并校验归档内容。
- 显式指定目标平台,并断言 RepoTag、architecture、Entrypoint/CMD、OCI version/revision labels。打开导出的 TAR,检查
manifest.json指向目标镜像,而不只检查 ZIP 中存在某个 TAR 文件名。 - 归档镜像必须就是通过
start-dev健康验收的 image ID/digest;若发布脚本使用另一标签或重新构建,必须比较 ID/digest,或对最终发布镜像重新执行同等 Compose 健康验收。 - 覆盖既有归档、删除中间 TAR 或更新版本文件前,遵循用户授权;可行时把中间文件移入回收站。
- 不把
.env.production、数据目录、日志、输出结果、虚拟环境或已有镜像归档复制进镜像上下文。
7. 分层验证和诊断
按成本从低到高验证:
- 静态语法与文件存在性。
docker compose ... config或start-dev -ConfigOnly。- Dockerfile 构建和构建期 smoke checks。
start-dev启动、健康等待和日志检查。- API 最小请求、挂载读写、重启行为与镜像归档校验。
诊断时至少区分:
- Compose/环境错误:缺变量、路径格式、端口、服务名或挂载不匹配。
- 镜像构建错误:锁文件、系统包、源码校验、补丁换行、编译或复制路径失败。
- 应用错误:入口、模块导入、配置、算法运行或结果解析失败。
- Docker 基础设施错误:layer extract I/O、daemon 连接、磁盘/WSL 存储损坏。此类错误先验证 daemon 和存储,不通过修改业务代码掩盖。
完成标准
- 所有打包文件共同表达同一个应用入口、内部端口、镜像标签和路径契约。
- 唯一版本源、标签转换和允许的部署覆盖已声明;其他版本漂移会阻断发布。
.dockerignore没有排除 Dockerfile 构建所需的补丁、源码或资源。- 原生构建可复现,补丁换行和上游版本已固化。
start-dev -ConfigOnly通过。- 经用户许可后,
.\scripts\start-dev.ps1 -EnvFile .\.env.production构建成功、容器健康并输出最终 URL。 - 若要求离线交付,归档镜像与已验收镜像的 ID/digest 一致,或最终发布镜像已重新通过同等健康验收;目标 TAR/ZIP 的 manifest 与元数据已验证,且文档中的命令与实际脚本一致。