跳转到内容

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

每个服务对应一个独立的容器:

服务作用必要性
databasePostgreSQL + pgvector 向量存储必须
sandboxTerrarium 代码执行沙箱推荐(支持 /code 命令)
searchSearXNG 开源搜索引擎推荐(支持 /online 命令)
computer浏览器自动化环境可选(支持 /operator 命令)
serverKhoj 主应用必须

GitHub Actions CI/CD

Khoj 使用 11 个 workflow 文件管理 CI/CD:

Workflow触发条件作用
test.ymlpush/PR运行测试套件
pre-commit.ymlpush/PRLint 和类型检查
dockerize.ymlrelease tag构建并推送 Docker 镜像到 GHCR
pypi.ymlrelease tag发布到 PyPI
desktop.ymlrelease tag构建桌面客户端
release.yml手动Release 管理
build_khoj_el.ymlpush构建 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 启动。

参考来源