From c53d67da2fc45889bdf361941a5b0d1b5506ddbc Mon Sep 17 00:00:00 2001 From: disqualifier Date: Tue, 30 Jun 2026 04:40:10 -0400 Subject: [PATCH] add uv as a parallel option alongside pip MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit uv is presented as the recommended faster, standards-compliant drop-in; pip stays the baseline/fallback. uv command syntax verified against docs.astral.sh/uv before writing. environments.md: - new 'uv (optional, faster)' section: install from pyproject (uv pip install . / -e . / '.[dev]'), the uv sync managed-venv flow, and a note that uv.lock is local-only/gitignored (not committed) - pinning subsection: pin direct deps in pyproject via == or git @ref, with a warning that this pins direct deps only — transitive deps still float at build time (documented tradeoff) - uv equivalents added beside pip in the Local .venv and Docker tabs deploy.md: - 'Faster builds with uv' tip: uv-from-ghcr COPY, ENV UV_COMPILE_BYTECODE=1, uv pip install --system . (reads pyproject, no lock) - layer-caching shows the uv variant beside the pip one - checklist notes uv pip install + UV_COMPILE_BYTECODE; fix stray 'configs' plural -> 'config' .gitignore: ignore uv.lock (local-only, never committed). Verified in-browser; mkdocs build --strict clean (anchors resolve). Signed-off-by: disqualifier --- .gitignore | 1 + docs/deploy.md | 47 +++++++++++++++++++++++++++++----- docs/environments.md | 61 +++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 98 insertions(+), 11 deletions(-) diff --git a/.gitignore b/.gitignore index eddf307..c60d7f5 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ __pycache__/ *.py[cod] .venv/ venv/ +uv.lock # Playwright MCP run artifacts .playwright-mcp/ diff --git a/docs/deploy.md b/docs/deploy.md index 6900828..8e34e9e 100644 --- a/docs/deploy.md +++ b/docs/deploy.md @@ -40,6 +40,32 @@ CMD ["python", "-m", "yourapp"] 4. `chmod -R a+rwX /app` makes the app tree writable by **any** uid — that's what "uid-agnostic" means. +!!! tip "Faster builds with uv (optional)" + [uv](https://docs.astral.sh/uv/) is a drop-in for pip that reads the same + `pyproject.toml` — no lockfile needed in the image. Swap the deps layer and add + `UV_COMPILE_BYTECODE` so containers don't pay the first-import `.pyc` compile + cost: + + ```dockerfile + FROM python:3.12-slim + COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/ # (1)! + ENV HOME=/tmp + ENV UV_COMPILE_BYTECODE=1 # (2)! + + WORKDIR /app + COPY pyproject.toml . + RUN uv pip install --system . # (3)! + COPY . . + RUN chmod -R a+rwX /app + CMD ["python", "-m", "yourapp"] + ``` + + 1. Pull the `uv` binary from its published image — no pip-installing uv itself. + 2. Compile bytecode at build time so the container doesn't eat the first-import + `.pyc` compile cost on every cold start. + 3. `--system` installs into the image's Python (no venv needed — the container + *is* the isolation); reads `pyproject.toml`, no `uv.lock` required. + ```yaml services: yourapp: @@ -92,17 +118,23 @@ All `/srv/...` paths are owned by the `services` user (uid/gid **1337**). ## Layer caching -Copy `requirements.txt` and `pip install` **before** `COPY . .`. Docker caches -layers in order, so deps only reinstall when `requirements.txt` changes — not on -every code edit. Get this backwards and every one-line change triggers a full -dependency reinstall. +Copy the deps file and install **before** `COPY . .`. Docker caches layers in +order, so deps only reinstall when the deps file changes — not on every code edit. +Get this backwards and every one-line change triggers a full dependency reinstall. +Same principle whether you use pip or uv: ```dockerfile -COPY requirements.txt . +COPY requirements.txt . # pip baseline RUN pip install --no-cache-dir -r requirements.txt # cached until deps change COPY . . # changes every build ``` +```dockerfile +COPY pyproject.toml . # uv path +RUN uv pip install --system . # cached until deps change +COPY . . # changes every build +``` + ## Subprocess and browser workloads Bots that spawn Chrome, Xvfb, ffmpeg, or other child processes need three extra @@ -133,14 +165,15 @@ services: ## What your compose / Dockerfile needs - `user: "1337:1337"` -- bind mounts for **configs + logs** +- bind mounts for **config + logs** - named volumes for **the rest** - secrets bind-mounted **`:ro`** - `HOME=/tmp` - `chmod -R a+rwX /app` -- deps installed **before** the code copy (layer caching) +- deps installed **before** the code copy (layer caching) — pip or `uv pip install` - `git` in the image **if the container needs it** - for browser/subprocess workloads: `init: true`, `shm_size`, `mem_limit` +- using uv? add `ENV UV_COMPILE_BYTECODE=1` ## Secrets diff --git a/docs/environments.md b/docs/environments.md index 7206d92..0f4b391 100644 --- a/docs/environments.md +++ b/docs/environments.md @@ -21,7 +21,14 @@ happens, depending on where the project runs: ```bash python -m venv .venv # create it (once) source .venv/bin/activate # activate for this shell - pip install -r requirements.txt + pip install -e . # install the project from pyproject.toml + ``` + + Or with [uv](#uv-optional-faster) — same `pyproject.toml`, much faster: + + ```bash + uv venv # create .venv + uv pip install -e . # install from pyproject.toml ``` Keep `.venv/` **gitignored** — it's per-machine, never committed. @@ -55,13 +62,14 @@ happens, depending on where the project runs: ```dockerfile FROM python:3.12-slim - COPY requirements.txt . - RUN pip install --no-cache-dir -r requirements.txt + COPY pyproject.toml . + RUN pip install --no-cache-dir . # or: uv pip install . COPY . . ``` This is how things run in production — see the [Deploy guide](deploy.md) for the - full container standard (uid 1337, mounts, layer caching). + full container standard (uid 1337, mounts, layer caching, and the uv image + setup). !!! tip "Which one?" **Local `.venv`** for quick iteration, **Makefile** when you want repeatable @@ -69,6 +77,51 @@ happens, depending on where the project runs: exclusive — a project often has a `.venv` for local dev *and* a Dockerfile for deploy. +## uv (optional, faster) + +!!! tip "uv is the recommended fast path; pip stays the baseline" + [uv](https://docs.astral.sh/uv/) is a faster, standards-compliant drop-in for + pip. It reads the **same `pyproject.toml`** — no workflow change required, and + `pip` keeps working exactly as before. Use it wherever you'd reach for pip; the + rest of this handbook shows the pip command with the uv equivalent beside it. + +Install deps straight from `pyproject.toml` (no `requirements.txt` needed): + +```bash +uv pip install . # install the project + its deps +uv pip install -e . # editable (dev) install +uv pip install '.[dev]' # with an extras group, e.g. dev +``` + +If you'd rather have uv manage the venv for you, use the managed-venv flow: + +```bash +uv sync # create/refresh .venv from pyproject.toml + uv.lock +uv run python -m yourapp # run inside the managed env, no manual activate +``` + +!!! note "`uv.lock` is local-only — never committed" + `uv sync` writes a `uv.lock` for your machine's resolved environment. It is + **gitignored**, not committed — we don't ship a lockfile. Pinning happens in + `pyproject.toml` (below), not the lock. + +### Pinning deps + +Pin **direct** dependencies in `pyproject.toml` with `==` or a git `@ref`: + +```toml +[project] +dependencies = [ + "requests==2.31.0", + "mylib @ git+https://git.rethinkstudios.io/rethink-public/mylib.git@", +] +``` + +!!! warning "This pins direct deps only — transitive deps still float" + `==` / `@ref` pins the packages **you** list. Their dependencies still resolve + fresh at build time. That's an accepted tradeoff — documented on purpose — not + an oversight: we pin what we depend on directly and let the rest float. + ## Local dev with pyenv For local work you also need the right **Python version**, not just isolated deps.