diff --git a/AGENTS.md b/AGENTS.md index ff359686..06b830b4 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -103,6 +103,8 @@ Notes ## Containers & Volumes - Code is bind-mounted read-only into containers (`.:/app:ro`) to avoid writes into the repo from inside Docker. +- The dev `Dockerfile` installs the packages `uv.lock` pins into `/opt/venv` before it copies the code, so a code + change doesn't reinstall them. A dependency change in `pyproject.toml` needs `uv lock` before the image builds. - Runtime artifacts (Django `STATIC_ROOT`, `MEDIA_ROOT`, Celery beat schedule) write under `/data` backed by the `appdata` named volume. - Generate migrations on the host (e.g., `uv run python qb_site/manage.py makemigrations`) and commit them, rather than creating files from inside containers. - `Dockerfile.production` is the production image, separate from the dev `Dockerfile`/Compose setup: packages from diff --git a/Dockerfile b/Dockerfile index be2808b5..939430ee 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,28 +1,38 @@ # syntax=docker/dockerfile:1 -FROM python:3.12-slim +# Development image, for docker-compose.yml, which mounts the repo over /app. The production image +# is Dockerfile.production; this one starts from the same base image and the same uv.lock. + +FROM python:3.12.14-slim-trixie@sha256:f77ac9e44ae96ef2c90b8053ea08c31f8be030f824196b0ae4db6d462c84e51f + +COPY --from=ghcr.io/astral-sh/uv:0.12.19@sha256:04d046b13e60d6bcec73cbc5e1cad25d680dea90c8573340950a0ac2d1aef424 /uv /bin/uv ENV PYTHONDONTWRITEBYTECODE=1 \ PYTHONUNBUFFERED=1 \ - PIP_NO_CACHE_DIR=1 \ - PIP_DISABLE_PIP_VERSION_CHECK=1 \ - PYTHONPATH=/app + PYTHONPATH=/app \ + PATH=/opt/venv/bin:$PATH \ + UV_LINK_MODE=copy \ + UV_PYTHON_DOWNLOADS=never \ + UV_PROJECT_ENVIRONMENT=/opt/venv WORKDIR /app -RUN apt-get update \ - && apt-get install -y --no-install-recommends \ - build-essential \ - libpq-dev \ - && rm -rf /var/lib/apt/lists/* +# Dependencies first, so that a change to the code doesn't reinstall them. The environment is +# outside /app, which Compose mounts over. --locked fails the build if uv.lock is out of date with +# pyproject.toml. +RUN --mount=type=cache,target=/root/.cache/uv \ + --mount=type=bind,source=pyproject.toml,target=pyproject.toml \ + --mount=type=bind,source=uv.lock,target=uv.lock \ + uv sync --locked --no-dev --no-install-project -COPY pyproject.toml README.md uv.lock* ./ +COPY pyproject.toml README.md uv.lock ./ COPY src ./src COPY qb_site ./qb_site COPY scripts ./scripts -RUN pip install --upgrade pip && \ - pip install --no-cache-dir -e . +# The project itself, editable, which puts src/ on the path. +RUN --mount=type=cache,target=/root/.cache/uv \ + uv sync --locked --no-dev RUN chmod +x scripts/docker-entrypoint.sh diff --git a/docs/sandboxed_docker_setup.md b/docs/sandboxed_docker_setup.md index f7bee2dd..58be4fc8 100644 --- a/docs/sandboxed_docker_setup.md +++ b/docs/sandboxed_docker_setup.md @@ -50,8 +50,8 @@ which it treats as fatal. It dies before doing any credential work, and BuildKit generic "error getting credentials". The helper runs only because `~/.docker/config.json` sets `"credsStore": "desktop"`. Every image -this stack pulls is public — `python:3.12-slim`, `postgres:16-alpine`, `redis:7-alpine` — so -nothing here needs a credential helper. +this stack pulls is public — `python:3.12.14-slim-trixie`, `ghcr.io/astral-sh/uv`, +`postgres:16-alpine`, `redis:7-alpine` — so nothing here needs a credential helper. **Read-only access to those paths does not help.** The failure is a write.