infra: deploy Open WebUI on hf-pdocker-01 with Ollama + Claude backends

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-11 14:13:16 -05:00
parent cd37bb9858
commit 59f529e3a7
6 changed files with 487 additions and 18 deletions
@@ -0,0 +1,244 @@
# Open WebUI Deployment Plan
> **For agentic workers:** REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Deploy Open WebUI on hf-pdocker-01 with Ollama + Anthropic Claude backends, publicly accessible at `open.hobbs.farm`.
**Architecture:** Single-container Compose service on hf-pdocker-01. Traefik file-based dynamic config for HTTPS routing. NAS-backed persistent storage. Prometheus blackbox probes for monitoring.
**Tech Stack:** Open WebUI (ghcr.io/open-webui/open-webui:main), Traefik v3.6 (file provider), Prometheus blackbox exporter.
**Spec:** `docs/superpowers/specs/2026-03-11-openwebui-design.md`
---
## Chunk 1: Create Files and Deploy
### Task 1: Create Compose File
**Files:**
- Create: `compose/hf-pdocker-01/openwebui/docker-compose.yml`
- [ ] **Step 1: Create the compose file**
```yaml
services:
open-webui:
image: ghcr.io/open-webui/open-webui:main
container_name: open-webui
restart: unless-stopped
ports:
- "3010:8080"
env_file:
- .env
environment:
- TZ=America/Chicago
- OLLAMA_BASE_URL=http://192.168.86.196:11434
volumes:
- /mnt/docker-data/configs/openwebui:/app/backend/data
mem_limit: 1G
memswap_limit: 1G
labels:
- "com.centurylinklabs.watchtower.enable=false"
healthcheck:
test: ["CMD-SHELL", "curl -sf http://localhost:8080/health || exit 1"]
interval: 30s
timeout: 10s
retries: 3
start_period: 60s
logging:
driver: "json-file"
options:
max-size: "50m"
max-file: "3"
```
### Task 2: Create Traefik Dynamic Config
**Files:**
- Create: `stacks/traefik/dynamic/openwebui.yml`
- [ ] **Step 1: Create the Traefik dynamic route file**
```yaml
http:
routers:
openwebui:
rule: "Host(`open.hobbs.farm`)"
entryPoints:
- websecure
service: openwebui
tls:
certResolver: letsencrypt
openwebui-http:
rule: "Host(`open.hobbs.farm`)"
entryPoints:
- web
middlewares:
- openwebui-https-redirect
service: openwebui
middlewares:
openwebui-https-redirect:
redirectScheme:
scheme: https
permanent: true
services:
openwebui:
loadBalancer:
servers:
- url: "http://192.168.86.192:3010"
```
### Task 3: Deploy to hf-pdocker-01
Uses `docker-swarm-ops` agent. All steps run on hf-pdocker-01 via SSH.
- [ ] **Step 1: Create data directory on NAS**
```bash
ssh glyptic@192.168.86.192 "sudo mkdir -p /mnt/docker-data/configs/openwebui"
```
- [ ] **Step 2: Create compose directory on NAS**
```bash
ssh glyptic@192.168.86.192 "sudo mkdir -p /mnt/docker-data/compose/hf-pdocker-01/openwebui"
```
- [ ] **Step 3: Generate WEBUI_SECRET_KEY and create .env file**
```bash
SECRET=$(openssl rand -hex 32)
# Get ANTHROPIC_API_KEY from existing ai-agents .env on NAS
API_KEY=$(ssh glyptic@192.168.86.192 "grep ANTHROPIC_API_KEY /mnt/docker-data/compose/hf-pdocker-01/ai-agents/.env | cut -d= -f2")
ssh glyptic@192.168.86.192 "cat > /mnt/docker-data/compose/hf-pdocker-01/openwebui/.env << EOF
ANTHROPIC_API_KEY=$API_KEY
WEBUI_SECRET_KEY=$SECRET
EOF"
```
- [ ] **Step 4: SCP compose file to NAS**
```bash
scp compose/hf-pdocker-01/openwebui/docker-compose.yml glyptic@192.168.86.192:/mnt/docker-data/compose/hf-pdocker-01/openwebui/docker-compose.yml
```
- [ ] **Step 5: SCP Traefik dynamic config to NAS**
```bash
scp stacks/traefik/dynamic/openwebui.yml glyptic@192.168.86.192:/mnt/docker-data/traefik/dynamic/openwebui.yml
```
- [ ] **Step 6: Pull image and start container**
```bash
ssh glyptic@192.168.86.192 "cd /mnt/docker-data/compose/hf-pdocker-01/openwebui && docker compose pull && docker compose up -d"
```
- [ ] **Step 7: Verify container health**
```bash
ssh glyptic@192.168.86.192 "docker ps --filter name=open-webui --format 'table {{.Names}}\t{{.Status}}'"
```
Expected: `open-webui Up ... (healthy)` (may take up to 60s for healthcheck)
- [ ] **Step 8: Verify internal access**
```bash
ssh glyptic@192.168.86.192 "curl -sf -o /dev/null -w '%{http_code}' http://localhost:3010"
```
Expected: `200`
- [ ] **Step 9: Verify Traefik picked up the route**
```bash
curl -sf -o /dev/null -w '%{http_code}' https://open.hobbs.farm
```
Expected: `200` (Traefik auto-reloads file-based config)
- [ ] **Step 10: Verify Ollama connectivity from container**
```bash
ssh glyptic@192.168.86.192 "docker exec open-webui curl -sf http://192.168.86.196:11434/api/tags | head -c 200"
```
Expected: JSON response listing available Ollama models (llava:13b)
## Chunk 2: Monitoring and Documentation
### Task 4: Add Prometheus Blackbox Probes
**Files:**
- Modify: `stacks/monitoring/prometheus.yml`
- [ ] **Step 1: Add internal probe to http-pdocker01 job**
Add `- http://192.168.86.192:3010 # Open WebUI` to the `http-pdocker01` targets list (after the Alertmanager line, line 70).
- [ ] **Step 2: Add external probe to http-external job**
Add `- https://open.hobbs.farm # Open WebUI` to the `http-external` targets list (after the Gitea line, line 158).
- [ ] **Step 3: SCP updated prometheus.yml to NAS**
```bash
scp stacks/monitoring/prometheus.yml glyptic@192.168.86.192:/mnt/docker-data/configs/prometheus/prometheus.yml
```
- [ ] **Step 4: Hot-reload Prometheus**
```bash
ssh glyptic@192.168.86.192 "curl -X POST http://localhost:9095/-/reload"
```
- [ ] **Step 5: Verify probes are up**
```bash
ssh glyptic@192.168.86.192 "curl -s 'http://localhost:9095/api/v1/targets?state=active' | python3 -c \"import sys,json; targets=json.load(sys.stdin)['data']['activeTargets']; [print(t['labels'].get('instance','')) for t in targets if 'open' in t['labels'].get('instance','').lower()]\""
```
Expected: Two lines showing `http://192.168.86.192:3010` and `https://open.hobbs.farm`
### Task 5: Update CLAUDE.md
**Files:**
- Modify: `CLAUDE.md`
- [ ] **Step 1: Add Open WebUI to hf-pdocker-01 service list**
Add `├── [COMPOSE] Open WebUI (:3010) -- LLM chat UI (Ollama + Claude)` to the hf-pdocker-01 section.
- [ ] **Step 2: Add to internal services list**
Add `- Open WebUI: :3010` under hf-pdocker-01 internal services.
- [ ] **Step 3: Add to public services list**
Add `- Open WebUI: \`open.hobbs.farm\`` under Public Services.
### Task 6: Update MEMORY.md
**Files:**
- Modify: `/home/glyptic/.claude/projects/-mnt-c-Users-Jeremy-projects-docker/memory/MEMORY.md`
- [ ] **Step 1: Add Open WebUI memory entry**
Add section with deployment details: node, port, compose path, LLM backends, Traefik route, data path.
### Task 7: Update Wiki
- [ ] **Step 1: Launch documentation-keeper agent (background)**
Update wiki with new Open WebUI service documentation.
### Task 8: Commit
- [ ] **Step 1: Commit all new/modified files**
```bash
git add compose/hf-pdocker-01/openwebui/docker-compose.yml stacks/traefik/dynamic/openwebui.yml stacks/monitoring/prometheus.yml CLAUDE.md
git commit -m "infra: deploy Open WebUI on hf-pdocker-01 with Ollama + Claude backends"
```
@@ -0,0 +1,148 @@
# Open WebUI Deployment Design
**Date:** 2026-03-11
**Node:** hf-pdocker-01 (192.168.86.192)
**Public URL:** https://open.hobbs.farm
**Internal URL:** http://192.168.86.192:3010
## Overview
Deploy Open WebUI as a standalone Compose service on hf-pdocker-01, connected to Ollama (local GPU machine) and Anthropic Claude API. Publicly accessible via Traefik at `open.hobbs.farm`.
## Architecture
```
Internet -> Traefik (hf-pdocker-01:443) -> http://192.168.86.192:3010 -> open-webui:8080
|
+-> Ollama (192.168.86.196:11434) [llava:13b + future models]
+-> Anthropic API (cloud) [Claude models]
```
## Service Definition
- **Image:** `ghcr.io/open-webui/open-webui:main`
- **Container name:** `open-webui`
- **Internal port:** 8080 (container default)
- **Host port:** 3010
- **Memory:** `mem_limit: 1G`, `memswap_limit: 1G`
- **Restart:** `unless-stopped`
- **Watchtower:** `com.centurylinklabs.watchtower.enable=false` (manual upgrades)
- **Timezone:** `TZ=America/Chicago`
### Compose Location
- Repo: `compose/hf-pdocker-01/openwebui/docker-compose.yml`
- Deploy target: `/mnt/docker-data/compose/hf-pdocker-01/openwebui/`
- `.env` file alongside compose for secrets (ANTHROPIC_API_KEY, WEBUI_SECRET_KEY)
### Persistent Storage
- Data volume: `/mnt/docker-data/configs/openwebui/` -> `/app/backend/data` in container
- Stores: user accounts, chat history, settings, uploaded files
- Open WebUI uses SQLite internally. Single-writer scenario on NFS is acceptable. If locking issues arise, fall back to local storage (like Guacamole's MySQL pattern at `/var/lib/`).
- Container runs as root by default; NFS write permissions should work without `user: "1024:100"` but will be verified during deploy.
### Environment Variables
- `OLLAMA_BASE_URL=http://192.168.86.196:11434` — native Ollama connection
- `ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}` — from .env, enables native Anthropic provider (Open WebUI v0.5+ has built-in Anthropic support)
- `WEBUI_SECRET_KEY=${WEBUI_SECRET_KEY}` — from .env, generated via `openssl rand -hex 32`
- `TZ=America/Chicago`
### Healthcheck
```yaml
healthcheck:
test: ["CMD-SHELL", "curl -sf http://localhost:8080/health || exit 1"]
interval: 30s
timeout: 10s
retries: 3
start_period: 60s
```
### Logging
```yaml
logging:
driver: "json-file"
options:
max-size: "50m"
max-file: "3"
```
### Traefik Configuration
Traefik on hf-pdocker-01 uses **file-based dynamic config** (no Docker provider/labels). A new dynamic config file is needed:
- **Repo path:** `stacks/traefik/dynamic/openwebui.yml`
- **Deploy target:** `/mnt/docker-data/traefik/dynamic/openwebui.yml` (auto-picked up by Traefik's file watcher)
```yaml
http:
routers:
openwebui:
rule: "Host(`open.hobbs.farm`)"
entryPoints:
- websecure
service: openwebui
tls:
certResolver: letsencrypt
openwebui-http:
rule: "Host(`open.hobbs.farm`)"
entryPoints:
- web
middlewares:
- openwebui-https-redirect
service: openwebui
middlewares:
openwebui-https-redirect:
redirectScheme:
scheme: https
permanent: true
services:
openwebui:
loadBalancer:
servers:
- url: "http://192.168.86.192:3010"
```
### Network
- Default bridge network (single-container service, no custom network needed)
- Traefik routes to `http://192.168.86.192:3010` via file-based config (no overlay network required)
### Authentication
- Built-in Open WebUI auth (email/password)
- First user to register becomes admin
- Admin disables open registration after initial setup
### Monitoring
- **Internal probe:** `http://192.168.86.192:3010` added to Prometheus `http-pdocker01` blackbox job
- **External probe:** `https://open.hobbs.farm` added to Prometheus `http-external` blackbox job
## LLM Backend Details
### Ollama (Native)
Open WebUI has first-class Ollama support. Setting `OLLAMA_BASE_URL` makes all Ollama models available in the model selector. Currently `llava:13b` is loaded; any future models pulled to Ollama will auto-appear.
### Anthropic Claude (Native)
Open WebUI v0.5+ has built-in Anthropic provider support. Setting the `ANTHROPIC_API_KEY` environment variable enables Claude models in the model selector. No proxy or OpenAI-compatibility layer needed. The existing API key (same one used by frigate-notify and AI agents) goes in the `.env` file.
## Post-Deploy Checklist
1. Verify container health and web UI loads at `http://192.168.86.192:3010`
2. Verify Traefik routes `https://open.hobbs.farm` correctly
3. Register first admin user
4. Disable open registration in admin settings
5. Verify Ollama models appear in model selector
6. Verify Claude models appear in model selector
7. Add Prometheus blackbox probes (internal + external)
8. Hot-reload Prometheus: `curl -X POST http://192.168.86.192:9095/-/reload`
9. Update CLAUDE.md service lists
10. Update MEMORY.md
11. Update wiki via documentation-keeper agent
12. Store WEBUI_SECRET_KEY in Vaultwarden