Files
ubuntu-post-install/services/ai-stack.md
T
Outis 8e7ffc5185 Merge pull request #392 from outis1one/claude/frigate-authelia-openid-0l1htj
Correct stale Mealie vision-import guidance in ai-stack.md
2026-08-24 12:02:25 -04:00

15 KiB

Roles

  • Open WebUI (chat, research, light coding) — local Ollama + any cloud providers in one model dropdown; wired to your code via the RAG + MCP servers and Gitea.
  • PaintPlus (separate paintplus service) — the front end for all image work (inpaint / upscale / generate). Point its AI_PROVIDER at a cloud API, or at this stack's local comfyui / invokeai for local image-gen.
  • Gitea + GitHub syncbash gitea-github-sync.sh mirrors repos both ways (pull GitHub → local git, or push local → GitHub).
  • RAG / MCP / Kiwix — retrieve just the relevant context so you feed the model less text (saves tokens), for both local and cloud models.
  • Web search uses DuckDuckGo (no SearXNG in this build).

Hybrid workflow — local coding model + Claude Code

Split coding work by size, not by tool preference. This stack's local Ollama coder model (the GPU generations table below has sizing per card) handles fast, in-loop iteration — autocomplete, boilerplate, single-file refactors, private/offline drafting, zero token cost. Claude Code (cloud) handles the bigger, longer, cross-file work — architectural refactors, anything needing full-repo context or stronger judgment — driven against this stack's Gitea (or GitHub, via the gitea-github-sync.sh mirror in Roles above).

Where to put instructions for each side

Claude Code loads CLAUDE.md in four tiers, concatenated broadest to most specific — later tiers add to earlier ones, they don't replace them:

Tier Path Put here
User ~/.claude/CLAUDE.md Your personal conventions, true on every project — e.g. "CLI menus are numbered, 0 is always exit," "verify UI changes with Playwright," your code-style rules
Project ./CLAUDE.md or ./.claude/CLAUDE.md This codebase's own architecture/conventions, shared with collaborators via git (this file is the reference example)
Local ./CLAUDE.local.md (gitignored) Your personal per-project notes — sandbox URLs, test data
One-off task The prompt itself, handed over when you say "go" The specific feature/idea for this build — never durable, don't put it in CLAUDE.md

Write cross-project quirks into ~/.claude/CLAUDE.md once — every project inherits them automatically, no per-repo duplication needed. If it grows past ~200 lines, split it into ~/.claude/rules/*.md (still user-level, loads before project-level rules).

Claude Code reading from self-hosted Gitea

Two levels, depending on what you need:

  • Plain git — works today, nothing to install. Claude Code's git operations are shell git commands, not a GitHub-specific code path — clone/push/pull against this stack's Gitea over SSH or an HTTPS token exactly like any other remote. This only applies to a locally-run Claude Code CLI against your own machine; a cloud/remote Claude Code session (like the one used to write this doc) is scoped to whichever provider — typically GitHub — it was attached to at session start, and can't reach an arbitrary self-hosted Gitea on your LAN.
  • PR/issue/CI-level integration (optional). Reading/commenting on Gitea PRs and issues the way a GitHub MCP server does for GitHub needs an MCP server that speaks Gitea's REST API. Gitea's own project publishes one — gitea/gitea-mcp (gitea.com/gitea/gitea-mcp) — as a binary release, a Docker image (docker.gitea.com/gitea-mcp-server), or go run gitea.com/gitea/gitea-mcp@latest; it supports both stdio and HTTP transport. Generate a token first — this stack's Gitea → profile → Settings → Applications → Generate New Token (repo/api scopes) — then:
    # stdio — simplest, one Claude Code CLI on this box
    claude mcp add gitea --env GITEA_HOST=http://localhost:3001 \
        --env GITEA_ACCESS_TOKEN=<token> -- gitea-mcp -t stdio
    
    # or HTTP — one server, shared by multiple Claude Code clients
    gitea-mcp -t http --port 8090 &        # run once, e.g. alongside the stack
    claude mcp add gitea http://localhost:8090/mcp \
        --header "Authorization: Bearer <token>"
    
    Not bundled by default — this stack's Gitea has no built-in Claude integration out of the box; this is you adding it.

GPU switcher (small local GPU only)

One small GPU can't run local chat and local image-gen at once. Swap it:

~/docker/ai-stack/gpu-mode.sh images   # before generating locally in PaintPlus
~/docker/ai-stack/gpu-mode.sh llm       # back to local chat in Open WebUI
~/docker/ai-stack/gpu-mode.sh status    # see which is active

Cloud models work anytime and need no swap.

Service URLs

Service URL Auth
Open WebUI http://localhost:3000 built-in (first visit = admin)
InvokeAI http://localhost:9090 none
ComfyUI http://localhost:8188 none
Kiwix http://localhost:8181 none
Gitea http://localhost:3001 built-in
Portainer https://localhost:9443 built-in

Manage the stack

cd ~/docker/ai-stack
bash start.sh          # pull latest images + docker compose up -d
bash stop.sh           # docker compose down
bash status.sh         # GPU / container / RAG health
bash pull-models.sh    # pull Ollama models (run once after first install)

Also a systemd unit: sudo systemctl {start,stop,status} local-ai

Vision models (image understanding)

None of the tier-selected chat/code models above can read an image. pull-models.sh offers one optional vision model at the end — pick it there, or pull one manually any time:

docker exec ollama ollama pull moondream   # or llava:7b / qwen2.5vl:7b / llama3.2-vision:11b
Model Size Notes
moondream ~1.7 GB By Moondream AI — tiny, built for CPU-only or weak/old-GPU hardware. Best default if you don't have a real GPU.
llava:7b ~4.7 GB General-purpose vision, moderate resources.
qwen2.5vl:7b ~6 GB Stronger accuracy, needs more RAM/VRAM.
llama3.2-vision:11b ~7.9 GB Meta's vision model — heaviest of these four.

Point any OpenAI-compatible app's vision/image-import feature at this stack's Ollama endpoint with the pulled model. For Mealie's "import recipe from photo" specifically — checked against docs.mealie.io directly, since Mealie moved this off env vars at some point and old OPENAI_* env var guidance for it is now stale: it's configured live in the UI, not .envGroup Settings → AI Providers in Mealie itself, not this stack's .env or docker-compose.yml. Add a provider with:

  • base_url: http://host.docker.internal:11434/v1 (Ollama publishes on the host at 0.0.0.0:11434, and Mealie is a separate compose project not sharing a network with this stack, so it has to be reached over the host the same way Caddy reaches bridge-mode services — see Mealie's own compose: add extra_hosts: ["host.docker.internal:host-gateway"] to its mealie: service if that hostname doesn't already resolve there. The host's real LAN IP works too with no compose edit, just less stable across DHCP renewals.)
  • api_key: any non-empty placeholder — required by Mealie's form, ignored by Ollama.
  • model: the vision model just pulled (e.g. moondream).

Then mark that provider as the one used for image recognition (a separate toggle from the general default-provider setting) — that's what actually turns on the photo-import feature. No Mealie container restart needed, it applies live. See Open WebUI → Settings → Connections if you'd rather confirm the local base URL/model name there first.

NVIDIA server-GPU generations — capability reference

What a given datacenter GPU generation can actually run through this stack (Ollama for chat/code, ComfyUI/InvokeAI for images), since it's VRAM- and tensor-core-bound per generation. Only Ampere and newer have native BF16 tensor cores; llama.cpp/Ollama's CUDA backend supports Pascal (compute capability 6.0) and up, so quantized chat/coding model size mostly comes down to VRAM capacity — older cards just run slower per token, with no flash-attention-class kernel path.

Generation Example server cards VRAM Flux 2 (32B DiT) Flux.1 / SDXL Chat (GGUF, Ollama) Coding (GGUF, Ollama)
Blackwell (2024-25) B100 / B200 / GB200 180-192GB HBM3e Yes — FP8 fast, native Yes, fast 70B+ at high precision, easily Any coder model, full precision
Hopper (2022) H100 / H200 80-141GB HBM3 Yes — FP8 native tensor cores; the target generation Yes, fast 70B in Q4-Q8 comfortably Qwen2.5-Coder-32B / DeepSeek-Coder-V2, full precision
Ampere (2020) A100 40/80GB 40-80GB HBM2e Minimum viable — FP8 checkpoint (~32GB) fits the 80GB card; no native FP8 tensor cores, so it's upcast/emulated rather than accelerated Yes, comfortable (native BF16/TF32) 70B Q4 (~40GB) fits the 80GB card with room; 30-34B comfortable on the 40GB card Qwen2.5-Coder-32B / Codestral-22B comfortable
Volta (2017) V100 16/32GB 16-32GB HBM2 No — even the 32GB card has no headroom for the FP8 checkpoint plus activations FLUX.1-dev FP8 (~18-23GB) fits the 32GB card, tight; SDXL/SD1.5 fine (first-gen FP16 tensor cores) 32GB card: 30-34B Q4 comfortable, 70B tight/needs multi-GPU. 16GB card: 13-14B comfortable 32B coder models fit the 32GB card in Q4
Pascal (2016) P100 16GB / P40 24GB 16-24GB HBM2/GDDR5 No SD1.5 fine; SDXL runs but slow — no tensor cores at all, weak/emulated FP16 (worse on the P40 than the P100) Same VRAM math as Ampere/Volta at matched capacity (P40 24GB ≈ 30B Q4), but noticeably slower tokens/sec 32B coder Q4 fits the P40 24GB capacity-wise; fine for batch/background, not snappy interactive autocomplete
Maxwell (2014) M40 / M60 24GB 8-24GB GDDR5 No Impractical — SD1.5 only, very slow; no real FP16 tensor path 7B-13B Q4 runs but slow 7B-class coder models only — a novelty, not a daily driver

CUDA 13 has already dropped Pascal/Volta (this happened, it's not a future warning anymore) — but that's the toolkit, not the driver, and it doesn't block this stack: Docker GPU passthrough only needs the host driver to recognize the card, since prebuilt inference images (Ollama, ComfyUI, etc.) already bundle whatever CUDA runtime they need internally. The driver is the part to get right. NVIDIA has named R580 the last driver branch that adds Volta/Pascal support (P100/P40/V100 explicitly listed), supported into ~June 2028 — pin to R580 explicitly rather than trusting ubuntu-drivers autoinstall's default pick on a fresh/newer Ubuntu install, since a later branch may no longer initialize these cards at all. Also confirm you land on the proprietary driver package, not an -open one — NVIDIA's open-source kernel modules only support Turing and newer, so Volta/Pascal require the closed-source module; ubuntu-drivers devices should recommend the right one for the card it detects, but double-check rather than assume on a distro release that defaults newer GPUs to -open. None of this is something require_docker handles — it installs Docker/Compose only; the NVIDIA driver and nvidia-container-toolkit are still on you to install first, and getting the driver branch right is what actually matters here, not the Ubuntu version itself.

"Tesla"-branded card power connector — don't assume standard PCIe. ("Tesla" here is NVIDIA's old datacenter-card brand name, retired after Volta — not the unrelated, much older Tesla microarchitecture that predates Fermi/Kepler/Maxwell/Pascal/Volta. V100/P100/P40/M40 all shipped under the Tesla brand despite being four different architecture generations.) These PCIe cards take an 8-pin CPU/EPS12V connector, not the 6+2-pin PCIe connector a normal GPU uses — a standard PCIe cable will not plug in. Get the dongle/adapter (splits a PCIe 8-pin into EPS12V, or use a real EPS cable) and never daisy-chain both 8-pin rails off one PSU cable/splitter — use two separate cable runs. These cards are also passively cooled (built for server chassis airflow, no onboard fan) — a tower case needs a shroud + dedicated fan blowing through the heatsink fins, and there's no display output, which is a non-issue on a headless box like this but worth knowing going in.

MoE models are the exception that gives Pascal/Volta real life for coding. The "coding" column above assumes dense models, where token speed tracks the full parameter count — exactly where Pascal/Volta's missing or first-gen tensor cores hurt most. A mixture-of-experts model breaks that link: VRAM is still set by total params (every expert has to be resident — no memory saving from sparsity), but compute per token is set by active params only. qwen3-coder:30b-a3b in ollama pull is the concrete case — 30B total, only ~3.3B active per token (128 experts, 8 routed) — so it needs the same ~19GB VRAM (Q4_K_M) as a dense 30B model but computes like a dense ~3B one. That's light enough that Pascal/Volta's weak tensor cores barely matter, making it the best coding model to put on a P40 24GB or a V100 — a dense 32B coder on the same card would be noticeably slower for no quality gain. Mixtral 8x7B (46.7B total / ~13B active, ~24-26GB at Q4) is the same trade at a larger size — fits Volta 32GB or Ampere, with the same active-vs-total gap.

Cloud LLM providers (Open WebUI)

Open WebUI uses an OpenAI-compatible connection list. The local RAG server is the first entry; any cloud providers added at install follow it. Two semicolon-separated lists in .env, matched by position (RAG must stay first):

# ~/docker/ai-stack/.env
OPENAI_API_BASE_URLS=http://rag-server:8001/v1;https://api.groq.com/openai/v1
OPENAI_API_KEYS=local-rag;gsk_xxx
cd ~/docker/ai-stack && docker compose up -d open-webui   # apply
Provider Base URL Key
Groq https://api.groq.com/openai/v1 https://console.groq.com/keys
DeepInfra https://api.deepinfra.com/v1/openai https://deepinfra.com/dash/api_keys
OpenAI https://api.openai.com/v1 https://platform.openai.com/api-keys
OpenRouter https://openrouter.ai/api/v1 https://openrouter.ai/keys

Alternatively, add them at runtime in Open WebUI → Settings → Admin → Connections (no file edits, survives image upgrades).

Update

Re-run the ai-stack installer (refreshes vendored source, keeps your .env), then bash ~/docker/ai-stack/start.sh. Or in place: cd ~/docker/ai-stack && bash local-ai-setup.sh --force.

Caddy

Open WebUI is reverse-proxied as open-webui:8080 on caddy_net (or your configured Caddy network name; attached with docker network connect after start). Other services are LAN-only by default — add Caddy site blocks for them if you want remote access.