add uv as a parallel option alongside pip

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 <dev@disqualifier.me>
This commit is contained in:
disqualifier 2026-06-30 04:40:10 -04:00
parent dafc1dcacd
commit c53d67da2f
3 changed files with 98 additions and 11 deletions

1
.gitignore vendored
View File

@ -9,6 +9,7 @@ __pycache__/
*.py[cod] *.py[cod]
.venv/ .venv/
venv/ venv/
uv.lock
# Playwright MCP run artifacts # Playwright MCP run artifacts
.playwright-mcp/ .playwright-mcp/

View File

@ -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 4. `chmod -R a+rwX /app` makes the app tree writable by **any** uid — that's what
"uid-agnostic" means. "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 ```yaml
services: services:
yourapp: yourapp:
@ -92,17 +118,23 @@ All `/srv/...` paths are owned by the `services` user (uid/gid **1337**).
## Layer caching ## Layer caching
Copy `requirements.txt` and `pip install` **before** `COPY . .`. Docker caches Copy the deps file and install **before** `COPY . .`. Docker caches layers in
layers in order, so deps only reinstall when `requirements.txt` changes — not on order, so deps only reinstall when the deps file changes — not on every code edit.
every code edit. Get this backwards and every one-line change triggers a full Get this backwards and every one-line change triggers a full dependency reinstall.
dependency reinstall. Same principle whether you use pip or uv:
```dockerfile ```dockerfile
COPY requirements.txt . COPY requirements.txt . # pip baseline
RUN pip install --no-cache-dir -r requirements.txt # cached until deps change RUN pip install --no-cache-dir -r requirements.txt # cached until deps change
COPY . . # changes every build 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 ## Subprocess and browser workloads
Bots that spawn Chrome, Xvfb, ffmpeg, or other child processes need three extra Bots that spawn Chrome, Xvfb, ffmpeg, or other child processes need three extra
@ -133,14 +165,15 @@ services:
## What your compose / Dockerfile needs ## What your compose / Dockerfile needs
- `user: "1337:1337"` - `user: "1337:1337"`
- bind mounts for **configs + logs** - bind mounts for **config + logs**
- named volumes for **the rest** - named volumes for **the rest**
- secrets bind-mounted **`:ro`** - secrets bind-mounted **`:ro`**
- `HOME=/tmp` - `HOME=/tmp`
- `chmod -R a+rwX /app` - `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** - `git` in the image **if the container needs it**
- for browser/subprocess workloads: `init: true`, `shm_size`, `mem_limit` - for browser/subprocess workloads: `init: true`, `shm_size`, `mem_limit`
- using uv? add `ENV UV_COMPILE_BYTECODE=1`
## Secrets ## Secrets

View File

@ -21,7 +21,14 @@ happens, depending on where the project runs:
```bash ```bash
python -m venv .venv # create it (once) python -m venv .venv # create it (once)
source .venv/bin/activate # activate for this shell 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. Keep `.venv/` **gitignored** — it's per-machine, never committed.
@ -55,13 +62,14 @@ happens, depending on where the project runs:
```dockerfile ```dockerfile
FROM python:3.12-slim FROM python:3.12-slim
COPY requirements.txt . COPY pyproject.toml .
RUN pip install --no-cache-dir -r requirements.txt RUN pip install --no-cache-dir . # or: uv pip install .
COPY . . COPY . .
``` ```
This is how things run in production — see the [Deploy guide](deploy.md) for the 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?" !!! tip "Which one?"
**Local `.venv`** for quick iteration, **Makefile** when you want repeatable **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 exclusive — a project often has a `.venv` for local dev *and* a Dockerfile for
deploy. 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@<sha>",
]
```
!!! 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 ## Local dev with pyenv
For local work you also need the right **Python version**, not just isolated deps. For local work you also need the right **Python version**, not just isolated deps.