Dockerfile, compose.yml, env template, and runbook for the second-brain
web container. Targets herbys-dev (10.0.21.207); Traefik (file-provider
on 10.0.11.20) reaches the published host port at 10.0.21.207:8080.
Image:
- python:3.12-slim + the official uv binary copied from the upstream
image, plus apt-installed git + ca-certificates so the Gitea VCS pin
for embedding-chunking resolves at build time.
- uv sync --frozen --no-dev --no-install-project, source copy, then a
second uv sync to install the project itself. Two-step so the lock
install layer caches independently of source edits.
- No ffmpeg / claude CLI / faster-whisper — web role doesn't need any
of them. Extraction runs on the dev host's CLI; transcription on the
tower.
- Drops to uid 1000 (`app`) before CMD. Uvicorn binds 0.0.0.0:8000
inside the container, with --proxy-headers + --forwarded-allow-ips=*
so Traefik's X-Forwarded-* survive.
compose.yml:
- Joins the existing external `homelab` bridge network so the container
reaches homelab-postgres:5432 and ollama:11434 by service DNS.
- Publishes the uvicorn port at 10.0.21.207:8080 (LAN IP bound, not
0.0.0.0) for Traefik on the separate VM to reach. NO traefik.* labels
— file-provider Traefik can't read them.
- env_file: .env (0600, gitignored) — SECOND_BRAIN_DATABASE_URL points
at homelab-postgres:5432 (containerised), NOT the host's 127.0.0.1:5433
port-map.
- restart: unless-stopped.
README.md:
- Build + bring-up commands.
- SECURITY note: no SSO / no CSRF / mutating endpoints — Traefik route
must be LAN-only for now (Travis's call).
- The two infra steps Travis applies himself, with ready-to-paste
snippets:
- Knot DNS: brain.herbylab.dev → 10.0.11.20.
- Traefik dynamic config: file-provider router + service block.
- Verification checklist for both before-and-after-DNS states.
Live-verified on herbys-dev: container Up, dashboard returns 200 with
real status counts from petalbrain, settings save round-trip works,
no tracebacks in logs.
deploy/tower/:
- second-brain-transcribe.service — systemd unit. User=herbyadmin,
Type=simple, After=/Wants= wg-quick@wg-lan.service so the WG tunnel
must come up first. Restart=always with a StartLimitBurst guard.
- second-brain-transcribe.env.example — env file template documenting
the SECOND_BRAIN_DATABASE_URL form for db.wg.herbylab.dev (10.99.0.1)
and the optional WHISPER_* overrides.
- README.md — EndeavourOS install steps (nvidia/cuda/cudnn, ffmpeg, uv
+ tower extra, model pre-warm), WG topology reference, validation
checklist for what to confirm once the tunnel is live, and a
follow-ups section flagging the local-disk → NAS media migration as
out-of-scope-for-this-round.
Tests:
- tests/test_claim.py — live-DB race test. Two threads call
claim_next_source against a single PULLED video row; SKIP LOCKED
must give exactly one of them the row, the other gets None. Also
asserts the claimed_by/at columns land + release nulls them.
Auto-skips when no SECOND_BRAIN_DATABASE_URL is set.
- tests/test_transcribe.py — pure-Python coverage of resolve_settings
(cpu→int8, cuda→float16, env-over-block) and write_srt; plus a CPU
smoke test that synthesizes a numpy audio array and runs the `tiny`
model on cpu/int8 (auto-skipped when faster-whisper isn't installed,
i.e. on the dev side without --extra tower).