06. Infra & Local Ops¶
Local development topology¶
graph TD
classDef default fill:#1e293b,stroke:#38bdf8,stroke-width:2px,color:#f8fafc
classDef highlight fill:#065f46,stroke:#34d399,stroke-width:2px,color:#f0fdf4
classDef warning fill:#78350f,stroke:#fbbf24,stroke-width:2px,color:#fffbeb
Dev["Developer machine"]:::highlight
FE["Vite :5173"]
API["Uvicorn :8000"]
CW["Celery worker"]
RD["Redis :6380<br/>passworded"]
Jobs["./jobs"]
Paddle["Optional Paddle VL<br/>:1234"]:::warning
Dev --> FE
FE --> API
API --> RD
CW --> RD
API --> Jobs
CW --> Jobs
CW --> Paddle
linkStyle default stroke:#64748b,stroke-width:2px
Make targets (high signal)¶
From project Makefile:
| Target | Effect |
|---|---|
make setup |
uv sync + frontend pnpm install |
make up / down / nuke |
Redis via docker-compose.yml |
make backend-start |
FastAPI + Celery in background with PID files |
make frontend-start |
Vite on FRONTEND_PORT (5173) |
make start / stop / restart |
Full local stack |
make test |
pytest |
make fmt |
black + isort |
make docs-serve |
Project MkDocs on :8001 |
make health |
Health check helper |
Prereqs: uv, pnpm, Docker for Redis.
Compose files¶
docker-compose.yml (infra only)¶
- Redis 7 Alpine with
--requirepass, AOF, host port${REDIS_PORT:-6380} - Volume
./volumes/redis_data - Healthcheck uses password
docker-compose-full.yml (API + worker + Redis)¶
- Redis on 6379 without password
backendandworkerimages,platform: linux/amd64- Shared
./volumes/jobs→/app/jobs REDIS_URL=redis://redis:6379/0plus.envfile
Confirmed footgun: local compose expects passworded Redis on 6380; full compose uses open Redis on 6379. .env REDIS_URL must match the stack you start.
Docker images¶
| File | Base | CMD |
|---|---|---|
Dockerfile.backend |
ghcr.io/astral-sh/uv:python3.13-bookworm-slim |
uvicorn app.main:app --host 0.0.0.0 --port 8000 |
Dockerfile.celery |
same | celery ... worker --queues ocr --pool solo |
Both install OpenCV/Paddle system libs (libgl1, libglib2.0-0, libgomp1) and uv sync --frozen.
Apple Silicon note from README.md: enable Rosetta for amd64 emulation; Paddle lacks official Linux ARM64 wheels.
Environment variables¶
See .env.example. Critical groups:
- Redis connectivity
VLM_MODE/VLM_MODEL/ Paddle server URL- Provider API keys (NVIDIA NIM for LiteLLM, NVIDIA OCR, Sarvam)
- Concurrency and timeouts
JOBS_DIR,PORT,HOST,ALLOWED_HOSTSCELERY_CONCUR(Makefile-oriented; Docker worker uses solo pool)
Networking and ports¶
| Service | Default port |
|---|---|
| FastAPI | 8000 |
| Vite | 5173 |
| Redis (local compose) | 6380 → 6379 |
| Redis (full compose) | 6379 |
| Project docs | 8001 |
| Paddle VL server (example) | 1234 |
Startup / health¶
- API lifespan pings Redis and optionally prewarms models
GET /health→{"status":"ok","service":"universal-ocr"}- Celery prewarms on worker process init when enabled
Secrets handling¶
- Secrets expected in
.env(gitignored pattern in project) - Example file documents keys but leaves values empty
- Redis password only in local compose path
Observability¶
- Stdout/file logs via Makefile PID +
logs/directory conventions - No OpenTelemetry / Prometheus instrumentation found in inspected code
- Progress observability is product-facing via Redis status fields
Infra interview Q&A¶
Q: How do you run this on a Mac with Apple Silicon?
A: Prefer local make start with Redis container, or full compose under linux/amd64 + Rosetta; Paddle wheels drive the amd64 requirement.
Q: How do API and worker share artifacts?
A: Same JOBS_DIR / bind-mounted volume so the API can serve files the worker wrote.
Q: Why solo pool?
A: Heavy OCR + native model state; solo avoids fork issues with Paddle/OpenCV in the worker process (Inferred from Docker CMD and model singleton design).