```mermaid graph LR %% ── External ────────────────────────────────────────────────────────── NF([novelfire.net]) KK([Kokoro-FastAPI TTS]) LE([Let's Encrypt]) CL([Browser / iOS App]) %% ── Init containers ─────────────────────────────────────────────────── subgraph INIT["Init containers (one-shot)"] MI[minio-init\nmc: create buckets] PI[pb-init\nbootstrap collections] end %% ── Storage ─────────────────────────────────────────────────────────── subgraph STORAGE["Storage"] MN[(MinIO :9000\nchapters · audio\navatars · browse)] PB[(PocketBase :8090\nbooks · chapters_idx\naudio_cache · progress\nscrape_jobs · app_users · ranking)] VK[(Valkey :6379\npresign URL cache\nTTL-based · shared)] MS[(Meilisearch :7700\nindex: books)] end %% ── Application ─────────────────────────────────────────────────────── subgraph APP["Application"] CD[Caddy :443/:80\nreverse proxy\nauto-HTTPS] BE[Backend API :8080\nGo HTTP server] RN[Runner :9091\nGo background worker\n/metrics endpoint] UI[SvelteKit UI :3000\nadapter-node] end %% ── Init → Storage ──────────────────────────────────────────────────── MI -.->|create buckets| MN PI -.->|bootstrap schema| PB %% ── App → Storage ───────────────────────────────────────────────────── BE -->|blobs| MN BE -->|structured records| PB BE -->|cache presigned URLs| VK RN -->|chapter markdown & audio| MN RN -->|read/update jobs & books| PB RN -->|index books on scrape| MS UI -->|read presign cache| VK %% ── App internal ────────────────────────────────────────────────────── UI -->|REST API| BE %% ── Caddy routing ───────────────────────────────────────────────────── CD -->|/* proxy| UI CD -->|/api/* /health proxy| BE CD -->|/s3/* proxy internal| MN %% ── Runner → External ───────────────────────────────────────────────── RN -->|scrape HTTP GET| NF RN -->|TTS HTTP POST| KK CD -->|ACME certificate| LE %% ── Client ──────────────────────────────────────────────────────────── CL -->|HTTPS :443 single entry| CD ```