Files
stack/infra/images/api.Dockerfile
kert 9b58e2dfb2
Some checks failed
CI / lint (push) Successful in 35s
CI / notebooks-smoke (push) Successful in 1m34s
Deploy / notebooks (push) Has been skipped
Deploy / zotero (push) Has been skipped
Deploy / docs (push) Has been skipped
Deploy / api (push) Successful in 54s
Deploy / mc (push) Has been skipped
Infra CI / notebooks (push) Successful in 58s
Infra CI / zotero (push) Successful in 14s
Infra CI / docs (push) Successful in 1m38s
Infra CI / api (push) Successful in 13s
Infra CI / mc (push) Successful in 14s
Deploy / report (push) Successful in 12s
CI / test (push) Has been cancelled
fix(api,notebooks): unwedge /health, unlock zotero reads, marimo chart crash, #515 flake
The api container had been unhealthy for ~12h with ~1500 leaked
healthcheck zombies. Root cause: /health -> _check_bib ->
list_items()[:1] hydrates the ENTIRE bib store via one get() per row
(~140s at the 181k items the zotero sync reached) and never closes the
connection — every hit pinned a threadpool thread until the pool (40)
was exhausted and the event loop had nothing left to respond with.

- bib.Store.list_items: SQL-level limit= param; count() is now a single
  COUNT query (was len(list_items()) — O(n) get() calls); shared
  _filter_clause builder
- api _check_bib: bounded probe (limit=1) + explicit close;
  /bib/items pushes its limit into SQL instead of slicing after a
  full scan
- api.Dockerfile healthcheck: curl --max-time 4 — docker's timeout only
  stops waiting; the probe process previously lived on forever
- conf.connect.zotero(): mode=ro&immutable=1 — the running Zotero app
  holds the db lock nearly permanently, so plain ro opens fail with
  'database is locked' (nb issue #557)
- notebooks: unwrap mo.ui.altair_chart in skin_subs/acodb explorers —
  marimo 0.23.13 _get_binned_fields crashes on list-valued tooltip
  encodings ('list' object has no attribute 'get', nb issue #548)
- ci test job: preinstall duckdb sqlite extension before pytest —
  xdist workers raced INSTALL in ~/.duckdb (#515, 3 flaked runs)
2026-07-10 13:36:39 -04:00

40 lines
1.3 KiB
Docker

# syntax=docker/dockerfile:1
FROM ghcr.io/astral-sh/uv:python3.13-bookworm-slim
WORKDIR /app
# Local package registry (Gitea) — set via --build-arg to pull from mirror
ARG PYPI_INDEX_URL=""
# Patch base image CVEs + install curl for healthcheck.
# (Python cold-start on this image is 10-13s — too slow for the 5s
# healthcheck timeout, so Docker marks the container unhealthy even
# though /health responds in milliseconds.)
RUN apt-get update && apt-get upgrade -y && apt-get install -y --no-install-recommends curl && rm -rf /var/lib/apt/lists/*
# Copy project files for install
COPY pyproject.toml uv.lock README.md ./
COPY src/ src/
# Install the package (no dev deps)
ENV UV_PYTHON_PREFERENCE=only-system \
UV_LINK_MODE=copy \
UV_PROJECT_ENVIRONMENT=.venv \
UV_INDEX_URL=${PYPI_INDEX_URL}
RUN uv sync --no-dev && uv pip install -e .
# Config
COPY stack.toml ./
EXPOSE 8000
# --max-time bounds curl itself: docker's timeout only stops *waiting* —
# the probe process lives on, and a wedged server once accumulated ~1500
# hung healthcheck zombies this way.
HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
CMD curl -sf --max-time 4 http://localhost:8000/health || exit 1
CMD ["uv", "run", "--no-sync", "uvicorn", "api.server:app", \
"--host", "0.0.0.0", "--port", "8000", \
"--workers", "1", "--log-level", "info"]