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]
.venv/
venv/
uv.lock
# Playwright MCP run artifacts
.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
"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

View File

@ -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@<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
For local work you also need the right **Python version**, not just isolated deps.