Reviewed 6 September 2026

Part II: Practices

Packaging and distribution

Backend Notes
Hatchling Default in uv init; plugin system, version from file or VCS.
setuptools The backend most existing projects already declare; required where the build has to run arbitrary Python, as complex C extension builds do.
maturin Builds wheels for projects containing compiled extensions.
scikit-build-core CMake-based builds for C/C++ extensions.
flit-core Minimal backend for pure-Python packages.

Distribution formats: wheels for installation, source distributions for build-from-source. cibuildwheel builds and tests binary wheels across platforms and Python versions in CI. Applications are more often distributed as container images or as PEX/shiv archives than as wheels.

An application’s image installs from the lockfile in two steps, dependencies first, so a change to the source rebuilds only the last layer:

FROM python:3.14-slim AS build
COPY --from=ghcr.io/astral-sh/uv:<version> /uv /bin/
ENV UV_COMPILE_BYTECODE=1 UV_LINK_MODE=copy UV_NO_DEV=1 UV_PYTHON_DOWNLOADS=0
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 . .
RUN --mount=type=cache,target=/root/.cache/uv \
    uv sync --locked --no-editable

FROM python:3.14-slim
COPY --from=build /app/.venv /app/.venv
ENV PATH="/app/.venv/bin:$PATH"
CMD ["example"]

The final stage carries the virtual environment and nothing else: no uv, no cache, no source. That works because --no-editable installs the project into the environment instead of linking back to /app, and UV_PYTHON_DOWNLOADS=0 keeps uv on the image’s own interpreter, which the final stage has at the same path. A .dockerignore listing .venv keeps a local environment out of COPY . ., and the uv tag is pinned like any other dependency. uv’s Docker guide covers the variations.

A published library assembles these into one shape: uv init --lib, a src/ layout, the Hatchling backend, and a CI matrix listing each version in requires-python explicitly. requires-python follows SPEC 0. The public API is fully annotated and ships a py.typed marker so consumers get type information. Documentation is built with MkDocs and mkdocstrings from the same docstrings, and versioned with mike. The lockfile pins the development environment only; dependency ranges in [project.dependencies] stay wide, because a library that pins its dependencies is unusable alongside anything else. Hypothesis covers the invariants of the core data structures, and --doctest-modules keeps README and docstring examples correct.

References