# OpenWebUI OpenWebUI is a self-hosted chat interface for LLMs. It connects to OpenAI-compatible endpoints (vLLM, llama.cpp, Ollama) and adds RAG, plugins, tool calling, and multi-user support. **Host:** Dankclaw — **Port:** 3005 (host) → 8080 (container) — **Vhost:** `ai.franksdank.cloud` routes straight to `192.168.0.143:3005` (unguarded — OpenWebUI enforces its own auth) — **Boot-persistent:** Quadlet — **Docs:** https://github.com/open-webui/open-webui ## Facts | Item | Value | |------|-------| | Image | `ghcr.io/open-webui/open-webui:main` (verified from the live quadlet unit) | | Port | 3005 (host) → 8080 (container). Port 3000 is Gitea's HTTP, so it is not usable. | | Data volume | `/home/dank/Containers/open-webui/data` → `/app/backend/data` | | Backends | Three fleet endpoints: `frank-large` (host.containers.internal:7770), `frank-medium` (192.168.0.235:7770), `frank-small` (host.containers.internal:7771) — see [The LLM backends](#the-llm-backends) | | Network | Pinned bridge `systemd-open-webui` (`10.89.3.0/24`, gateway `10.89.3.1`) — see [History](#history) | | Persistence | Quadlet unit `open-webui.service`; `[Install] WantedBy=default.target` | | Deploy | `scripts/deploy-open-webui-quadlet.sh dankclaw` | ## The LLM backends OpenWebUI serves the three fleet LLM endpoints. The model picker is built from the `model` table in `webui.db` (one row per model), NOT from live connection discovery — so the picker shows exactly the models seeded there. | Picker model | Connection URL | Runtime | Host | |--------------|----------------|---------|------| | `frank-large` | `http://host.containers.internal:7770/v1` | unsloth llama.cpp (Strix Halo iGPU, 200k ctx) | Dankclaw | | `frank-medium` | `http://192.168.0.235:7770/v1` | vllm-radiance TP=2 (FP8, DFlash2, 262k ctx) | Dankserver | | `frank-small` | `http://host.containers.internal:7771/v1` | llama.cpp (RTX 3090, Gemma4-12B, 262k ctx) | Dankclaw | Two gotchas make the wiring non-obvious: - **llama.cpp `/v1/models` is Ollama-shaped.** llama.cpp returns `{"models":[{"name":...}]}`; OpenWebUI's `extract_data()` only reads the OpenAI `{"data":[{"id":...}]}` shape, so connection discovery silently misses the llama.cpp endpoints (frank-large, frank-small). The fix is to set `model_ids` in each connection's `api_config` — when non-empty, OpenWebUI builds the model list directly and never fetches `/v1/models`. frank-medium (vLLM) serves the OpenAI shape and would discover fine, but uses `model_ids` too for consistency. - **The host's own services are reached via `host.containers.internal`.** The container sits on a netavark bridge; it can hairpin to a *different* host's LAN IP (dankserver `192.168.0.235`) but not to its *own* host's non-published ports via the host LAN IP (`192.168.0.143:7770/7771` is refused). `host.containers.internal` (169.254.1.2) resolves to the host on the bridge and reaches both llama.cpp services. The Ollama connection is disabled (`ollama.enable=false`). > All three endpoints are bare-metal systemd services managed by this > repository. Model swaps, context sizing, and spec tuning happen there. See > the [LLM knowledge base](../LLM/index.md). ## Web search Chat web search is served by [[SearXNG]] on this host, wired in the quadlet (`ENABLE_WEB_SEARCH`, `WEB_SEARCH_ENGINE=searxng`, `SEARXNG_QUERY_URL`). - **Access path:** the backend calls `http://192.168.0.143:8081/search?format=json` directly — gluetun's published port, NOT the `search.franksdank.cloud` vhost. The Anubis→Authentik guard chain is a browser mechanism (proof-of-work + session flow); a plain HTTP client cannot solve it, and the internal port bypasses Caddy entirely. - **Limiter:** SearXNG caps non-HTML (JSON) requests at 4/IP/hour (`API_MAX`, a hardcoded Python constant — no config knob). The `pass_ip` list is checked FIRST and short-circuits the whole botdetection chain, and OpenWebUI's bridge egress (`10.89.x`) is in `pass_ip`, so the cap never applies. Verified: an 8-request burst from the container returned 8×200. - **Config storage:** OpenWebUI keeps web-search settings per-key in its SQLite config table (`webui.db`, `web.search.*`). The quadlet env vars seed that table on first start (fill-missing only); afterwards the DB is authoritative and Admin-UI edits persist there. ## Boot persistence (Quadlet) The unit lives at `dankclaw/open-webui/quadlet/open-webui.container`. On the host it installs to `~/.config/containers/systemd/open-webui.container`. The generator turns it into `open-webui.service`. Because the unit sets `[Install] WantedBy=default.target` and user linger is on, it starts at boot. `systemctl --user is-enabled open-webui.service` reports `generated` (the quadlet boot-start state). ## Volumes | Container path | Host path | |----------------|-----------| | `/app/backend/data` | `/home/dank/Containers/open-webui/data` | The data dir holds the database, RAG indexes, and uploads. The deploy script creates it if missing and preserves it across reruns. ## Healthcheck The quadlet carries a built-in healthcheck (30s interval, 3 retries, 300s start period). It checks two things from inside the container: 1. **App liveness** — `GET http://127.0.0.1:8080/health` returns 200 (`{"status":true}`). 2. **Network liveness** — a non-loopback interface exists in `/sys/class/net`. The dead-tap zombie state (History) leaves only `lo` behind, and this is the check that catches it. `podman ps` shows the container as `unhealthy` if either fails. Probe manually at `http://dankclaw:3005/health`. ## Retired instances - The compose `open-webui` service (port 3000) was never run: 3000 is Gitea's HTTP port, so it could not bind. - The bare-container `open-webui` on Dankserver (port 3000) pointed at a dead backend (`:9090`) and was removed. Its data dir is preserved on Dankserver in case it is needed. - The bare-container `searxng` on Dankserver (port 8081) was removed. SearXNG now runs only on Dankclaw behind Caddy. ## Gotchas - The image tracks upstream `main`; pin a digest in the quadlet unit if reproducible rollback matters more than tracking. (The old "CPU-only host" framing is stale: this APU runs the gfx1151 ROCm build — see [[ComfyUI]].) - The default admin account is created on first login; secure it immediately. - If the backend is loading (3–8 minute model load), OpenWebUI shows connection errors until the model is ready. - The on-host LLM services (frank-large `:7770`, frank-small `:7771`) are reached via `host.containers.internal`, NOT the host LAN IP: the container's netavark bridge cannot hairpin to its own host's non-published ports via `192.168.0.143` (connection refused), and the bridge gateway (`10.89.3.1`) lives in the rootless netns while llama-server runs in the host netns, so it refuses too. `host.containers.internal` (169.254.1.2) resolves to the host on the bridge and works — verified end-to-end for both endpoints. The Dankserver backend (frank-medium) is a different host, reached via its LAN IP `192.168.0.235:7770`. ## History - **2026-08-22 — slirp4netns → pinned bridge.** The unit ran on `Network=slirp4netns`. The slirp4netns helper process died (no OOM, no kill entry, no crash log found), and the container kept running with only `lo` in its netns: published port 3005 accepted TCP and reset every request, so chat served empty responses for ~2 days. Nothing logged an error. Moving to a kernel bridge removes the per-container helper entirely, and the healthcheck's interface check makes the zombie state visible if it ever returns. - **2026-09-21 — model picker rebuilt for the 3-model fleet.** The picker carried 39 stale rows (37 dead Ollama models + 2 `frank-default-*`) and was missing `frank-medium`. Rebuilt the `model` table to exactly `frank-large`/`frank-medium`/`frank-small` and set `model_ids` on each connection (llama.cpp's Ollama-shaped `/v1/models` is invisible to OpenWebUI's discovery). Pointed the two on-host endpoints at `host.containers.internal` — the bridge refuses the host LAN IP for non-published ports. All three verified end-to-end via `/openai/chat/completions`. ## See Also - [[Dankclaw Stack]] - [[Hermes]] — the agent OpenWebUI can talk to - [[SearXNG]] — the search engine on this host - [LLM knowledge base](../LLM/index.md) — the vLLM and llama.cpp endpoints