在 Docker 中使用 uv
快速入门
Tip
请查看 uv-docker-example 项目,了解在 Docker 中使用 uv 构建应用的最佳实践示例。
uv 提供了 distroless(无发行版)Docker 镜像和基于流行基础镜像的衍生镜像。distroless 镜像适用于将 uv 二进制文件复制到你自己的镜像构建中,而衍生镜像适用于在容器中使用 uv。distroless 镜像仅包含 uv 二进制文件,不包含其他任何内容。相比之下,衍生镜像包含预装 uv 的操作系统。
例如,在基于 Debian 的镜像中运行 uv:
可用镜像
以下 distroless 镜像可供使用:
ghcr.io/astral-sh/uv:latestghcr.io/astral-sh/uv:{major}.{minor}.{patch},例如ghcr.io/astral-sh/uv:0.11.23ghcr.io/astral-sh/uv:{major}.{minor},例如ghcr.io/astral-sh/uv:0.8(最新的补丁版本)
以下衍生镜像可供使用:
- 基于
alpine:3.23:ghcr.io/astral-sh/uv:alpineghcr.io/astral-sh/uv:alpine3.23
- 基于
alpine:3.22:ghcr.io/astral-sh/uv:alpine3.22
- 基于
debian:trixie-slim:ghcr.io/astral-sh/uv:debian-slimghcr.io/astral-sh/uv:trixie-slim
- 基于
buildpack-deps:trixie:ghcr.io/astral-sh/uv:debianghcr.io/astral-sh/uv:trixie
- 基于
dhi.io/alpine-base:3.23:ghcr.io/astral-sh/uv:alpine-dhighcr.io/astral-sh/uv:alpine3.23-dhi
- 基于
dhi.io/debian-base:trixie-debian13:ghcr.io/astral-sh/uv:debian-dhighcr.io/astral-sh/uv:trixie-dhi
- 基于
dhi/python:3.x:ghcr.io/astral-sh/uv:python3.14-dhighcr.io/astral-sh/uv:python3.13-dhighcr.io/astral-sh/uv:python3.12-dhighcr.io/astral-sh/uv:python3.11-dhighcr.io/astral-sh/uv:python3.10-dhi
- 基于
python3.x-alpine:ghcr.io/astral-sh/uv:python3.14-alpineghcr.io/astral-sh/uv:python3.14-alpine3.23ghcr.io/astral-sh/uv:python3.13-alpineghcr.io/astral-sh/uv:python3.13-alpine3.23ghcr.io/astral-sh/uv:python3.12-alpineghcr.io/astral-sh/uv:python3.12-alpine3.23ghcr.io/astral-sh/uv:python3.11-alpineghcr.io/astral-sh/uv:python3.11-alpine3.23ghcr.io/astral-sh/uv:python3.10-alpineghcr.io/astral-sh/uv:python3.10-alpine3.23ghcr.io/astral-sh/uv:python3.9-alpineghcr.io/astral-sh/uv:python3.9-alpine3.22
- 基于
python3.x-trixie:ghcr.io/astral-sh/uv:python3.14-trixieghcr.io/astral-sh/uv:python3.13-trixieghcr.io/astral-sh/uv:python3.12-trixieghcr.io/astral-sh/uv:python3.11-trixieghcr.io/astral-sh/uv:python3.10-trixieghcr.io/astral-sh/uv:python3.9-trixie
- 基于
python3.x-slim-trixie:ghcr.io/astral-sh/uv:python3.14-trixie-slimghcr.io/astral-sh/uv:python3.13-trixie-slimghcr.io/astral-sh/uv:python3.12-trixie-slimghcr.io/astral-sh/uv:python3.11-trixie-slimghcr.io/astral-sh/uv:python3.10-trixie-slimghcr.io/astral-sh/uv:python3.9-trixie-slim
与 distroless 镜像一样,每个衍生镜像都带有 uv 版本标签发布,格式为
ghcr.io/astral-sh/uv:{major}.{minor}.{patch}-{base} 和
ghcr.io/astral-sh/uv:{major}.{minor}-{base},例如 ghcr.io/astral-sh/uv:0.11.23-alpine。
此外,从 0.8 版本开始,每个衍生镜像还将 UV_TOOL_BIN_DIR 设置为 /usr/local/bin,以便 uv tool install 在默认用户下按预期工作。
更多详情,请参阅 GitHub Container 页面。
安装 uv
使用上述预装 uv 的镜像之一,或者通过从官方 distroless Docker 镜像复制二进制文件来安装 uv:
或者,使用安装脚本:
FROM python:3.12-slim-trixie
# 安装脚本需要 curl(及证书)来下载发布包
RUN apt-get update && apt-get install -y --no-install-recommends curl ca-certificates
# 下载最新的安装脚本
ADD https://astral.sh/uv/install.sh /uv-installer.sh
# 运行安装脚本然后删除它
RUN sh /uv-installer.sh && rm /uv-installer.sh
# 确保已安装的二进制文件在 `PATH` 中
ENV PATH="/root/.local/bin/:$PATH"
请注意,这需要 curl 可用。
无论哪种方式,最佳实践是锁定到特定的 uv 版本,例如:
Tip
虽然上面的 Dockerfile 示例锁定到特定标签,但也可以锁定到特定的 SHA256。在需要可重现构建的环境中,锁定特定 SHA256 被认为是最佳实践,因为标签可能被移动到不同的提交 SHA 上。
或者,使用安装脚本:
安装项目
如果你使用 uv 来管理项目,可以将项目复制到镜像中并安装:
# 将项目复制到镜像中
COPY . /app
# 禁用开发依赖
ENV UV_NO_DEV=1
# 将项目同步到新环境中,同时断言锁文件是最新的
WORKDIR /app
RUN uv sync --locked
Important
最佳实践是在仓库中添加 .venv 到 .dockerignore 文件,以防止它被包含在镜像构建中。项目虚拟环境依赖于你的本地平台,应该在镜像中从头创建。
然后,设置默认启动应用:
Tip
最佳实践是使用中间层将依赖安装和项目本身分开,以改善 Docker 镜像构建时间。
完整示例请参见 uv-docker-example 项目。
使用环境
项目安装完成后,你可以通过将项目的二进制目录放在路径前面来_激活_项目虚拟环境:
或者,你可以使用 uv run 来执行任何需要该环境的命令:
Tip
或者,可以在同步之前设置 UV_PROJECT_ENVIRONMENT 设置,以安装到系统 Python 环境中,从而完全跳过环境激活步骤。
使用已安装的工具
要使用已安装的工具,请确保工具二进制目录在路径中:
$ docker run -it $(docker build -q .) /bin/bash -c "cowsay -t hello"
_____
| hello |
=====
\
\
^__^
(oo)\_______
(__)\ )\/\
||----w |
|| ||
Note
工具二进制目录的位置可以通过在容器中运行 uv tool dir --bin 命令来确定。
或者,可以将其设置为固定位置:
在容器中开发
开发时,将项目目录挂载到容器中非常有用。通过这种设置,对项目的更改可以立即反映到容器化服务中,而无需重新构建镜像。但是,重要的是_不要_将项目虚拟环境(.venv)包含在挂载中,因为虚拟环境是平台特定的,应该保留为镜像构建的那个。
使用 docker run 挂载项目
通过匿名卷将项目(工作目录)绑定挂载到 /app,同时保留 .venv 目录:
Tip
包含 --rm 标志是为了确保容器退出时容器和匿名卷都会被清理。
完整示例请参见 uv-docker-example 项目。
使用 docker compose 配置 watch
使用 Docker compose 时,可以使用更复杂的工具来进行容器开发。watch 选项提供了比绑定挂载更精细的控制,并支持在文件更改时触发对容器化服务的更新。
Note
此功能需要 Compose 2.22.0,该版本随 Docker Desktop 4.24 一起提供。
在你的 Docker compose 文件中配置 watch,以挂载项目目录但不同步项目虚拟环境,并在配置更改时重新构建镜像:
services:
example:
build: .
# ...
develop:
# 创建 `watch` 配置以更新应用
#
watch:
# 将工作目录同步到容器中的 `/app` 目录
- action: sync
path: .
target: /app
# 排除项目虚拟环境
ignore:
- .venv/
# 当 `pyproject.toml` 更改时重新构建镜像
- action: rebuild
path: ./pyproject.toml
然后,运行 docker compose watch 以使用开发设置运行容器。
完整示例请参见 uv-docker-example 项目。
优化
编译字节码
对于生产镜像,通常建议将 Python 源文件编译为字节码,因为这往往能改善启动时间(代价是增加安装时间和镜像大小)。
要启用字节码编译,请使用 --compile-bytecode 标志:
或者,你可以设置 UV_COMPILE_BYTECODE 环境变量,以确保 Dockerfile 中的所有命令都编译字节码:
Note
uv 只会编译_托管_ Python 版本的标准库,在 uv python install 期间进行。非托管 Python 版本的标准库是否预编译由发行方决定。例如,官方的 python 镜像没有预编译的标准库。
缓存
可以使用缓存挂载来提高跨构建的性能:
更改 UV_LINK_MODE 可以消除关于无法链接文件的警告,因为缓存和同步目标位于不同的文件系统上。
如果你不挂载缓存,可以通过使用 --no-cache 标志或设置 UV_NO_CACHE 来减小镜像大小。
默认情况下,托管的 Python 安装不会在安装前被缓存。可以结合缓存挂载使用 UV_PYTHON_CACHE_DIR:
ENV UV_PYTHON_CACHE_DIR=/root/.cache/uv/python
RUN --mount=type=cache,target=/root/.cache/uv \
uv python install
Note
缓存目录的位置可以通过在容器中运行 uv cache dir 命令来确定。
或者,可以将缓存设置为固定位置:
中间层
如果你使用 uv 来管理项目,可以通过 --no-install 选项将传递依赖的安装移到单独的层中,从而改善构建时间。
uv sync --no-install-project 将安装项目的依赖但不安装项目本身。由于项目经常变化,但其依赖通常是静态的,这可以节省大量时间。
# 安装 uv
FROM python:3.12-slim
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/
# 将工作目录切换到 `app` 目录
WORKDIR /app
# 安装依赖
RUN --mount=type=cache,target=/root/.cache/uv \
--mount=type=bind,source=uv.lock,target=uv.lock \
--mount=type=bind,source=pyproject.toml,target=pyproject.toml \
uv sync --locked --no-install-project
# 将项目复制到镜像中
COPY . /app
# 同步项目
RUN --mount=type=cache,target=/root/.cache/uv \
uv sync --locked
请注意,pyproject.toml 是确定项目根目录和名称所必需的,但项目_内容_直到最后的 uv sync 命令才会被复制到镜像中。
Tip
如果你想从同步中移除额外的特定包,请使用 --no-install-package <name>。
工作区中的中间层
如果你使用工作区,则需要进行一些更改:
- 在初始同步期间使用
--frozen而不是--locked。 - 使用
--no-install-workspace标志,该标志排除项目_以及_任何工作区成员。
# 安装 uv
FROM python:3.12-slim
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/
WORKDIR /app
RUN --mount=type=cache,target=/root/.cache/uv \
--mount=type=bind,source=uv.lock,target=uv.lock \
--mount=type=bind,source=pyproject.toml,target=pyproject.toml \
uv sync --frozen --no-install-workspace
COPY . /app
RUN --mount=type=cache,target=/root/.cache/uv \
uv sync --locked
如果没有每个工作区成员的 pyproject.toml 文件,uv 无法断言 uv.lock 文件是最新的,因此我们在初始同步期间使用 --frozen 而不是 --locked 来跳过检查。在所有工作区成员都被复制之后的下一次同步,仍然可以使用 --locked,并将验证锁文件对所有工作区成员是否正确。
非可编辑安装
默认情况下,uv 以可编辑模式安装项目和工作区成员,这样对源代码的更改会立即反映到环境中。
uv sync 和 uv run 都接受 --no-editable 标志,该标志指示 uv 以非可编辑模式安装项目,移除对源代码的任何依赖。
在多阶段 Docker 镜像的上下文中,--no-editable 可用于在一个阶段中将项目包含在同步的虚拟环境中,然后仅将虚拟环境(而不是源代码)复制到最终镜像中。
例如:
# 安装 uv
FROM python:3.12-slim AS builder
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/
# 在两个阶段中使用系统 Python
ENV UV_PYTHON_DOWNLOADS=0
# 将工作目录切换到 `app` 目录
WORKDIR /app
# 安装依赖
RUN --mount=type=cache,target=/root/.cache/uv \
--mount=type=bind,source=uv.lock,target=uv.lock \
--mount=type=bind,source=pyproject.toml,target=pyproject.toml \
uv sync --locked --no-install-project --no-editable
# 将项目复制到中间镜像中
COPY . /app
# 同步项目
RUN --mount=type=cache,target=/root/.cache/uv \
uv sync --locked --no-editable
FROM python:3.12-slim
# 复制环境,但不复制源代码
COPY --from=builder /app/.venv /app/.venv
# 运行应用
CMD ["/app/.venv/bin/hello"]
临时使用 uv
如果最终镜像中不需要 uv,可以在每次调用时挂载二进制文件:
使用 pip 接口
安装包
系统 Python 环境在此上下文中可以安全使用,因为容器已经是隔离的。可以使用 --system 标志在系统环境中安装:
要默认使用系统 Python 环境,请设置 UV_SYSTEM_PYTHON 变量:
或者,可以创建并激活虚拟环境:
RUN uv venv /opt/venv
# 自动使用虚拟环境
ENV VIRTUAL_ENV=/opt/venv
# 将环境中的入口点放在路径前面
ENV PATH="/opt/venv/bin:$PATH"
使用虚拟环境时,应在 uv 调用中省略 --system 标志:
安装 requirements 文件
要安装 requirements 文件,请将其复制到容器中:
安装项目
在同时安装项目和 requirements 时,最佳实践是将 requirements 的复制与其余源代码的复制分开。这样,项目的依赖(不经常变化)可以与项目本身(变化非常频繁)分开缓存。
COPY pyproject.toml .
RUN uv pip install -r pyproject.toml
COPY . .
RUN uv pip install -e .
验证镜像来源
Docker 镜像在构建过程中经过签名,以提供其来源证明。这些证明(attestation)可用于验证镜像是否来自官方渠道。
例如,你可以使用 GitHub CLI 工具 gh 来验证证明:
$ gh attestation verify --owner astral-sh oci://ghcr.io/astral-sh/uv:latest
Loaded digest sha256:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx for oci://ghcr.io/astral-sh/uv:latest
Loaded 1 attestation from GitHub API
The following policy criteria will be enforced:
- OIDC Issuer must match:................... https://token.actions.githubusercontent.com
- Source Repository Owner URI must match:... https://github.com/astral-sh
- Predicate type must match:................ https://slsa.dev/provenance/v1
- Subject Alternative Name must match regex: (?i)^https://github.com/astral-sh/
✓ Verification succeeded!
sha256:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx was attested by:
REPO PREDICATE_TYPE WORKFLOW
astral-sh/uv https://slsa.dev/provenance/v1 .github/workflows/build-docker.yml@refs/heads/main
这告诉你,该特定的 Docker 镜像是由官方的 uv GitHub 发布工作流构建的,并且自构建以来未被篡改。
GitHub 证明基于 sigstore.dev 基础设施构建。因此,你也可以使用 cosign 命令 来验证证明 blob 与 uv 的(多平台)清单:
$ REPO=astral-sh/uv
$ gh attestation download --repo $REPO oci://ghcr.io/${REPO}:latest
Wrote attestations to file sha256:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.jsonl.
Any previous content has been overwritten
The trusted metadata is now available at sha256:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.jsonl
$ docker buildx imagetools inspect ghcr.io/${REPO}:latest --format "{{json .Manifest}}" > manifest.json
$ cosign verify-blob-attestation \
--new-bundle-format \
--bundle "$(jq -r .digest manifest.json).jsonl" \
--certificate-oidc-issuer="https://token.actions.githubusercontent.com" \
--certificate-identity-regexp="^https://github\.com/${REPO}/.*" \
<(jq -j '.|del(.digest,.size)' manifest.json)
Verified OK
Tip
这些示例使用了 latest,但最佳实践是验证特定版本标签的证明,例如 ghcr.io/astral-sh/uv:0.11.23,或者(更好的做法)特定的镜像摘要,如 ghcr.io/astral-sh/uv:0.5.27@sha256:5adf09a5a526f380237408032a9308000d14d5947eafa687ad6c6a2476787b4f。