uv + Hatchling 构建系统与 Docker 多阶段构建
uv + Hatchling 构建系统与 Docker 多阶段构建
学习目标
理解 Khoj 如何使用 uv 包管理、Hatchling 构建后端、Docker 多阶段构建和 docker-compose 微服务编排。
项目实践
Python 构建系统
pyproject.toml 定义了完整的构建配置:
[build-system]requires = ["hatchling"]build-backend = "hatchling.build"
[project]name = "khoj"dynamic = ["version"] # 由 hatch-vcs 从 git tag 自动生成requires-python = ">=3.10"license = "AGPL-3.0"Hatchling + hatch-vcs:版本号从 git tag 自动生成。每次 release 打 tag 后,版本号自动更新。开发者不需要手动修改版本号。
uv 包管理:uv.lock 是 uv 生成的依赖锁定文件。相比 pip + requirements.txt,uv 提供:
- 更快的依赖解析和安装(Rust 实现)
- 依赖锁定(类似 npm 的 lockfile)
- 虚拟环境管理
Docker 多阶段构建
Khoj 的 Dockerfile 使用三个构建阶段:
关键优化:预编译 llama-cpp-python 和 PyTorch CPU wheel。这两个包在构建时需要编译 C/C++ 代码,耗时 10-30 分钟。预编译 wheel 将构建时间缩短到几分钟。
docker-compose 微服务编排
services: database: image: pgvector/pgvector:pg15 # PostgreSQL 15 + pgvector volumes: - khoj_db:/var/lib/postgresql/data
sandbox: image: ghcr.io/khoj-ai/terrarium # Python 代码沙箱
search: image: searxng/searxng # 开源搜索引擎
computer: image: khoj-computer # 浏览器自动化(可选) profiles: ["computer"] # 默认不启动
server: image: khoj depends_on: - database - sandbox - search每个服务对应一个独立的容器:
| 服务 | 作用 | 必要性 |
|---|---|---|
database | PostgreSQL + pgvector 向量存储 | 必须 |
sandbox | Terrarium 代码执行沙箱 | 推荐(支持 /code 命令) |
search | SearXNG 开源搜索引擎 | 推荐(支持 /online 命令) |
computer | 浏览器自动化环境 | 可选(支持 /operator 命令) |
server | Khoj 主应用 | 必须 |
GitHub Actions CI/CD
Khoj 使用 11 个 workflow 文件管理 CI/CD:
| Workflow | 触发条件 | 作用 |
|---|---|---|
test.yml | push/PR | 运行测试套件 |
pre-commit.yml | push/PR | Lint 和类型检查 |
dockerize.yml | release tag | 构建并推送 Docker 镜像到 GHCR |
pypi.yml | release tag | 发布到 PyPI |
desktop.yml | release tag | 构建桌面客户端 |
release.yml | 手动 | Release 管理 |
build_khoj_el.yml | push | 构建 Emacs 包 |
问题与规避
| 问题 | 影响 | 规避策略 |
|---|---|---|
| llama-cpp-python 编译失败 | Docker 构建中断 | 预编译 wheel 覆盖常见平台;或禁用该依赖 |
| docker-compose 服务启动顺序 | server 启动时 database 可能未就绪 | depends_on 确保启动顺序;健康检查确保 database 可连接 |
| uv.lock 与 pyproject.toml 不同步 | 依赖安装不一致 | CI 中检查 uv lock --check |
| AGPL-3.0 许可证限制 | 修改后必须开源 | 自部署无影响;商业部署需要遵守 AGPL |
设计取舍
为什么选择 uv 而非 Poetry 或 pip-tools?
uv 的核心优势是速度——依赖解析和安装比 pip 快 10-100 倍。对于 Khoj 这样有数十个依赖的项目,uv sync 通常只需几秒钟。Poetry 虽然也有锁定功能,但速度不如 uv。pip-tools 需要手动维护 .in 文件,增加了维护负担。
为什么 Dockerfile 基于 Ubuntu 而非 Alpine?
Alpine 镜像更小(5MB vs 77MB),但使用 musl libc 而非 glibc,很多 Python 包(特别是涉及 C 扩展的,如 PyTorch、llama-cpp-python)在 Alpine 上需要重新编译。Ubuntu 虽然基础镜像更大,但预编译 wheel 可以直接使用,构建时间显著缩短。对于 Khoj 这种依赖大量 C 扩展的项目,Ubuntu 是更实际的选择。
为什么 computer 服务默认不启动?
浏览器自动化环境需要额外的资源(Docker 容器 + 桌面环境),且不是所有用户都需要。通过 profiles: ["computer"] 标记为可选,用户需要时通过 docker compose --profile computer up 启动。