perf(docker): split external deps into a cacheable layer, swap pip for uv
Stage 3's `pip install /tmp/wheels/*.whl` re-resolved and re-downloaded
~50 transitive deps on every build, because the wheel filenames (and
thus the layer hash) changed on every version bump.
Two changes:
1. Emit /wheels/deps.txt in the build stage — external deps only,
notify-bridge-* siblings filtered out. The runtime stage installs
from this file first, so the cache key is the pyproject.toml deps
(stable across releases) instead of the local wheel filenames
(changes every release). Registry buildcache now serves the whole
install layer on version-only bumps.
2. Swap pip for uv (ghcr.io/astral-sh/uv:0.11.7). uv resolves and
downloads ~10x faster than pip; combined with a BuildKit cache
mount on /root/.cache/uv and UV_COMPILE_BYTECODE=1, a cold install
drops from ~60-90s to a few seconds.
Local wheels are now installed with --no-deps since externals are
already satisfied.
This commit is contained in:
+48
-4
@@ -1,3 +1,4 @@
|
||||
# syntax=docker/dockerfile:1.7
|
||||
# =============================================================================
|
||||
# Stage 1: Build frontend (SvelteKit static output)
|
||||
# =============================================================================
|
||||
@@ -14,7 +15,7 @@ COPY frontend/ ./
|
||||
RUN npm run build
|
||||
|
||||
# =============================================================================
|
||||
# Stage 2: Build Python wheels
|
||||
# Stage 2: Build Python wheels + extract external dependency list
|
||||
# =============================================================================
|
||||
FROM python:3.12-slim AS python-build
|
||||
|
||||
@@ -30,16 +31,59 @@ RUN python -m build packages/core/ --wheel --outdir /wheels
|
||||
COPY packages/server/ packages/server/
|
||||
RUN python -m build packages/server/ --wheel --outdir /wheels
|
||||
|
||||
# Emit /wheels/deps.txt with ONLY external (PyPI) deps — filter out
|
||||
# notify-bridge-* siblings, which are installed from local wheels below.
|
||||
# This file is the cache key for the external-deps install layer: as long as
|
||||
# pyproject.toml dependency lines don't change, the runtime install layer is
|
||||
# served from registry buildcache and no wheels are re-downloaded.
|
||||
RUN python <<'PY'
|
||||
import tomllib
|
||||
|
||||
deps: list[str] = []
|
||||
for p in ("packages/core/pyproject.toml", "packages/server/pyproject.toml"):
|
||||
with open(p, "rb") as f:
|
||||
data = tomllib.load(f)
|
||||
for d in data["project"].get("dependencies", []):
|
||||
if not d.lstrip().lower().startswith("notify-bridge-"):
|
||||
deps.append(d)
|
||||
|
||||
seen: set[str] = set()
|
||||
with open("/wheels/deps.txt", "w") as f:
|
||||
for d in deps:
|
||||
if d not in seen:
|
||||
seen.add(d)
|
||||
f.write(d + "\n")
|
||||
PY
|
||||
|
||||
# =============================================================================
|
||||
# Stage 3: Runtime
|
||||
# =============================================================================
|
||||
FROM ghcr.io/astral-sh/uv:0.11.7 AS uv
|
||||
|
||||
FROM python:3.12-slim
|
||||
|
||||
# uv — fast pip replacement; pinned version, not :latest
|
||||
COPY --from=uv /uv /uvx /bin/
|
||||
|
||||
ENV UV_COMPILE_BYTECODE=1 \
|
||||
UV_LINK_MODE=copy
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Install wheels
|
||||
COPY --from=python-build /wheels/ /tmp/wheels/
|
||||
RUN pip install --no-cache-dir /tmp/wheels/*.whl && rm -rf /tmp/wheels
|
||||
# Install external deps first — layer cache key is deps.txt content, which
|
||||
# only changes when pyproject.toml dependency lines change (not on version
|
||||
# bumps). The cache mount persists downloaded wheels across local rebuilds;
|
||||
# in CI, the registry buildcache serves the whole layer when unchanged.
|
||||
COPY --from=python-build /wheels/deps.txt /tmp/deps.txt
|
||||
RUN --mount=type=cache,target=/root/.cache/uv \
|
||||
uv pip install --system -r /tmp/deps.txt \
|
||||
&& rm /tmp/deps.txt
|
||||
|
||||
# Install local wheels without re-resolving — all external deps are present.
|
||||
COPY --from=python-build /wheels/*.whl /tmp/wheels/
|
||||
RUN --mount=type=cache,target=/root/.cache/uv \
|
||||
uv pip install --system --no-deps /tmp/wheels/*.whl \
|
||||
&& rm -rf /tmp/wheels
|
||||
|
||||
# Copy frontend build
|
||||
COPY --from=frontend-build /build/build/ /app/static/
|
||||
|
||||
Reference in New Issue
Block a user