All apps · 0 apps
OllamaUI
Docker app from chrizzo's Repository
Overview
Readme
View on GitHub
🦙 Ollama UI 🦙
Modern, reactive web interface for exploring Ollama models, browsing a scraped public catalog, pulling variants with streaming progress, and managing locally installed models.
What exactly is this? You’ll find a short, visual explanation in what_is/WHAT_IS.md (incl. Screenshots). 👈
Table of Contents
- Features
- Repository Layout
- Prerequisites
- Quick Start (UI Only)
- Host Resolution Logic
- API Routes Overview
- Frontend Architecture
- Python Scraper
- Development Workflow
- Deployment
- Troubleshooting
- Roadmap / Ideas
- Contributing
- License
- At A Glance
- Disclaimer / Infos
- Release Notes
1. Features ✨
- 🦙 Browse locally installed Ollama models (name, size, digest, modified date)
- ⏬ Pull / re-pull models (streamed NDJSON progress with derived percentage)
- 🗑️ Delete installed models
- 🌎 Searchable remote model catalog (slug, name & capabilities filtering)
- 🧩 Expandable variant lists with size info and one‑click pull
- 🔒 Global pull lock (avoids concurrent overwriting / race conditions)
- 🏠 Host configuration (cookie + header + env fallback resolution)
- 💬 Chat console with persisted sessions, personas, reasoning/tool-call traces, and a Compare mode to run two models side by side
- 🔌 Chat generation survives closing the tab — runs as a server-side job, reconnect from any tab/device to pick a live reply back up; a global "N generating" badge, toast and tab-title flash tell you when a background reply finishes
- 🛠️ Tool-calling for capable models —
web_searchvia SearXNG,get_current_date,get_weathervia Open-Meteo,calculator,create_reminder,create_recurring_task,list_scheduled_tasks,cancel_scheduled_task, plusremember_fact(memory, its own settings section). Each individually toggleable under Settings → Tools, all on by default; a tool turned off there stays off everywhere — web chat, Telegram, scheduled tasks - 🧠 Persistent memory — the assistant saves durable facts about you and recalls them automatically in future chats; on by default, toggle globally or per-chat
- ⏰ Scheduled tasks — recurring prompts that run automatically at a set time/days, no tab needed; each run lands as a new session with the usual background-job notification. One-off reminders can also be set directly from chat ("remind me tomorrow at 9...") via
create_reminder; a footer clock shows the server's own time since schedules run on it - 📱 Telegram bridge (opt-in) — chat with the app from your phone through a Telegram bot, using the same tool-calling/memory engine as the web UI, including sending photos to a vision model, voice messages (transcribed via a local
whisper.cppserver), documents (PDF/text/code — attach one to summarize or ask about it), tap-to-cancel buttons on/tasks, and/info//tasks//new//helpslash commands. Locked to a single allowlisted Telegram user id; setTELEGRAM_BOT_TOKEN,TELEGRAM_ALLOWED_USER_IDandTELEGRAM_MODELin.env.localto enable it (unset = bridge stays off), plusTELEGRAM_VISION_MODELfor photos andWHISPER_HOSTfor voice messages — the combined Docker image bundleswhisper-server+ a model automatically, soWHISPER_HOSTthere just defaults to it. Scheduled tasks and reminders push their result to Telegram too, not just into a new chat session, so they still reach you with no tab open — toggle this specifically under Settings → Telegram (on by default, independent of the bridge's own configuration). The polling loop backs off exponentially on a Telegram-side outage and auto-restarts if it ever crashes, with a "reconnected" notice once it recovers. The bridge's one persistent conversation is marked in the web UI's session list with a small paper-plane icon so it's not mistaken for an ordinary web chat - 🔁
create_recurring_task,list_scheduled_tasks,cancel_scheduled_tasktools — manage scheduled tasks and reminders entirely from chat (Telegram or web), no need to open the Scheduled page; in Telegram,/tasksalso offers a tap-to-cancel button per task. Every schedule-related claim (created, cancelled, or listed) gets verified against the actual tool-call trace instead of trusting the model's own "done" claim — a fabricated list is replaced outright with the real data rather than just flagged, since it's misinformation about your own data, not just an unconfirmed action - 🩺 Settings → Status panel — live reachability check for Ollama, the Whisper voice server and the Telegram bridge in one place, including a real Telegram
getMecall so a revoked/rotated bot token (which otherwise fails silently forever) shows up immediately instead of only as "nothing happens" when you message the bot - 📈 Model benchmark history — every real chat logs its speed automatically, plus an on-demand fixed-prompt benchmark across all installed models, with a trend chart
- 🗜️ Context compaction — summarize older chat history into a dense context note via the model itself (with undo)
- 📏 Honest context-window badge (real runtime
num_ctxfrom/api/ps, not the model's theoretical max) + per-model context slider up to the model's maximum - 🖥️ Running Models page — live
ollama psview with CPU/GPU memory split, context window, auto-unload countdown and one-click unload - 🎨 Theme-adaptive glass UI — 5 accent themes driving an ambient aurora background, glass cards, charts and scrollbars
- 📊 Dashboard with model stats and accessible, colorblind-safe charts
- 🔔 Toast notifications (success / error / info)
- ⚡️ Lightweight state management with Zustand & React Query caching, with per-message memoization and deferred markdown parsing so streaming stays smooth in long conversations
- 🐍 Python scraper (separate directory) to periodically refresh the catalog JSON
2. Repository Layout 🗂️
ollama-ui/ # Next.js (App Router) application
src/app/ # Pages & API routes
src/lib/ # Environment + utility helpers
src/store/ # Zustand stores (pull logs, toast, etc.)
models.json # Scraped catalog file (copied/updated manually)
Scraper/ # Python async scraper producing models.json
You run / build only inside ollama-ui/. The Python scraper is optional and only needed when you want to regenerate the catalog file.
3. Prerequisites 🛠️
- Node.js 22.5+ (required for the built-in
node:sqlitemodule used for persistence) - pnpm (preferred) OR npm / yarn / bun
- Python 3.11+ (only if you run the scraper)
- A reachable Ollama server (local or remote) exposing its HTTP API (
/api/pull,/api/tags, etc.)
4. Quick Start (UI Only) 🚦
cd ollama-ui
pnpm install # or npm install / yarn
pnpm dev # start dev server on http://localhost:3000
If you already have an Ollama instance running locally at the default fallback (see below) the Installed Models list should populate. Otherwise set the host in the UI or via environment.
5. Host Resolution Logic 🌐
Order of precedence (first valid wins):
- Request header:
x-ollama-host - Browser cookie:
ollama_host(set via the Host form) - Environment:
OLLAMA_HOSTorNEXT_PUBLIC_OLLAMA_HOST - Hardcoded fallback in
src/lib/env.ts
Validation enforces a full http:// or https:// URL.
Set via UI
Use the Host box on the Models page, enter full URL (e.g. http://localhost:11434) and press “Set host”. Cookie persists for 7 days.
Set via Env
Create .env.local in ollama-ui/:
OLLAMA_HOST=http://localhost:11434
Restart dev server.
Override Per Request
Send a custom header (useful for testing):
curl -H "x-ollama-host: http://other-host:11434" http://localhost:3000/api/models
6. API Routes Overview 📡
Base path: /api
| Route | Method | Purpose | Notes |
|---|---|---|---|
/api/models |
GET | List installed models + tags | Wraps Ollama /api/tags (server side implementation not shown here) |
/api/models/pull |
POST | Stream pull of a model or model:variant | Returns NDJSON, enriches lines with percentage when possible |
/api/models/delete |
POST | Remove a model | Body: { model: "name" } |
/api/models/catalog |
GET | Filtered catalog from models.json |
Query: q, limit (0 = all) |
/api/config/ollama-host |
GET/POST | Get or set resolved host | POST body: { host: string } |
Other routes (chat, stream, lamas, ps, tools/*) |
— | Additional functionality (not all documented yet) | Future docs TBD |
Pull Streaming Contract
/api/models/pull emits newline‑delimited JSON objects. Each line may contain:
{ status, digest?, total?, completed?, percentage? }
If total & completed exist but percentage is missing, the proxy computes and injects it.
Client logic (React) merges these events into a progress bar; a final { done: true } is appended.
7. Frontend Architecture 🏗️
- Next.js App Router: Server + edge runtime mixing (pull uses Edge for low latency, catalog read uses Node for FS access).
- React Query: Data caching & stale control for models and catalog.
- Zustand Stores: Lightweight stores for pull logs & toast queue.
- Streaming: Manual
ReadableStreamconsumption with incremental parsing of NDJSON lines. - Styling: Tailwind CSS (v4) + theme-adaptive glass design system (accent-driven aurora background, glass cards, scrollbars) with 5 color themes.
- Components: Reusable
<Button />with variants (primary,outline,danger, etc.).
State highlights:
anyPullActiveprevents concurrent pulls.expandedVariants[slug]toggles full variant list per model.- Progress derived from last event for the active model.
8. Python Scraper 🐍
Location: Scraper/
Purpose: Crawl public model pages, produce models.json with:
scraped_at- For each model:
slug,name,pulls,pulls_text,capabilities[],blurb,description,tags_count,variants[](each variant: tag, size, size_text, context tokens, input tokens)
Run
cd Scraper
python -m venv .venv && source .venv/bin/activate # one time
pip install -r requirements.txt
python ollama_scraper.py # full scrape
python ollama_scraper.py --limit 50 # first 50 models for quick test
Output: out/models.json. Copy or move that file into ollama-ui/models.json (overwrite existing) so the catalog endpoint serves it.
Schedule (Optional)
Use cron or a CI workflow to periodically update the file. Example cron entry (daily at 02:30):
30 2 * * * /usr/bin/bash -lc 'cd /path/to/repo/Scraper && source .venv/bin/activate && python ollama_scraper.py && cp out/models.json ../ollama-ui/models.json'
9. Development Workflow 🧑💻
Common scripts:
pnpm dev # start dev w/ Turbopack
pnpm build # production build
pnpm start # run built app
pnpm lint # eslint (uses flat config)
pnpm format # prettier write
After updating models.json, no restart is strictly required (catalog route reads file each request) but browser cache is bypassed anyway (cache: 'no-store'). Just refresh.
10. Deployment 🚀
You can deploy like any standard Next.js app (Vercel, Docker, etc.). Requirements:
- Ensure
models.jsonis present in the build output (it is read at runtime, so keep it in project root of the app). - Provide
OLLAMA_HOSTenvironment variable or rely on user-set cookie. - If deploying serverless, note: the catalog route uses Node runtime (filesystem). Ensure hosting platform supports reading that static file at runtime.
Docker (Combined Ollama + UI)
This repository now includes a multi‑stage Dockerfile at repo root that:
- Builds the Next.js app (standalone) with Node 20.
- Compiles
whisper.cpp'swhisper-serverfrom source in its own stage (speech-to-text for Telegram voice messages — Ollama has no audio-input support of its own) and downloads a multilingual model (~465MB; override with--build-arg WHISPER_MODEL_URL=...for a smaller/larger one, e.g.ggml-base.binorggml-medium.bin— avoid the.en-suffixed variants unless you only ever speak English to it). - Uses the official
ollama/ollama:latestimage as the final base. - Copies the standalone server + static assets + the compiled
whisper-server+ its model, plus the compiledinstrumentation.js(and the server chunk(s) it depends on) explicitly — Next's standalone output tracing doesn't include these on its own, which silently prevented the scheduler and Telegram bridge from ever starting in the built image (no error anywhere — confirmed live, see instrumentation.ts's own doc comment and the Dockerfile's comment at this COPY step for the full story). - Starts Ollama (
ollama serve),whisper-server(bound to127.0.0.1only — internal, never exposed) and the UI (node server.js) viastart.sh. - Also installs
ffmpeg(audio conversion for voice messages) andpoppler-utils(pdftotext, for reading PDF documents sent to the Telegram bridge) via apt-get in the final image.
Build & run:
docker build -t ollama-ui:latest .
docker run --rm -p 11434:11434 -p 3000:3000 ollama-ui:latest
Then open http://localhost:3000 (UI) and Ollama API at http://localhost:11434.
Docker Volumes: Persist Models & Database
To persist Ollama models and the UI database outside the container, mount host directories as volumes:
docker run --rm -p 11434:11434 -p 3000:3000 \
-v /path/to/ollama-models:/root/.ollama \
-v /path/to/ollama-ui-data:/app/data \
ollama-ui:latest
/root/.ollama: stores all pulled Ollama models (can be reused across containers/updates)/app/data: stores the SQLite database (app.db) for UI state (profiles, logs, etc.)
Docker Compose Example:
services:
ollama-ui:
image: ollama-ui:latest
build: .
ports:
- "11434:11434"
- "3000:3000"
volumes:
- /path/to/ollama-models:/root/.ollama
- /path/to/ollama-ui-data:/app/data
volumes: {}
Override default host the UI uses:
docker run --rm -e OLLAMA_HOST=http://localhost:11434 -p 11434:11434 -p 3000:3000 ollama-ui:latest
Telegram Bridge & Voice Transcription (Docker / Unraid)
.env.local (used elsewhere in this doc) only applies to pnpm dev — it's never baked into the image or read inside a container. In Docker, set the same variable names as regular container environment variables instead:
docker run --rm -p 11434:11434 -p 3000:3000 \
-v /path/to/ollama-models:/root/.ollama \
-v /path/to/ollama-ui-data:/app/data \
-e TELEGRAM_BOT_TOKEN=your-bot-token \
-e TELEGRAM_ALLOWED_USER_ID=your-telegram-user-id \
-e TELEGRAM_MODEL=llama3.1:8b \
ollama-ui:latest
Same idea in Docker Compose — add them under environment: (or env_file: pointing at a local file kept out of version control, so the token isn't sitting in docker-compose.yml itself):
services:
ollama-ui:
image: ollama-ui:latest
environment:
- TELEGRAM_BOT_TOKEN=your-bot-token
- TELEGRAM_ALLOWED_USER_ID=your-telegram-user-id
- TELEGRAM_MODEL=llama3.1:8b
On Unraid: edit the container → Add another Path, Port, Variable → type Variable, with TELEGRAM_BOT_TOKEN etc. as Key and the value as Value — no file involved, same as any other env var on that screen (this is also how OLLAMA_HOST gets set on Unraid).
Optional additions, same mechanism: TELEGRAM_VISION_MODEL (photos, only if TELEGRAM_MODEL itself doesn't already report vision support) and TELEGRAM_MODEL's tool-calling requirement carries over unchanged. Voice messages need nothing extra in the combined image — WHISPER_HOST already defaults to the bundled whisper-server (http://localhost:8790, see the Dockerfile section above); only set it yourself to point at a different/external Whisper server instead.
The bot token is a credential like any other — treat it the same way you'd treat OLLAMA_HOST credentials or a database password: not in docker-compose.yml committed to a repo, not pasted into shell history you'll publish, etc. If it ever leaks, revoke/regenerate it via @BotFather (/revoke or /token) — that's cheap and immediate.
Prebuilt Images (Combined Ollama + UI)
You can use prebuilt images from GitHub Container Registry (GHCR):
Pull and run:
docker pull ghcr.io/chrizzo84/ollamaui:latest
docker run --rm -p 11434:11434 -p 3000:3000 ghcr.io/chrizzo84/ollamaui:latest
If you want to disable the bundled Ollama server and point only to an external one, you can adapt start.sh to skip ollama serve and only run node server.js.
GPU Passthrough
Ollama can leverage GPUs inside the same container. Usage differs by platform:
NVIDIA (Linux) Prerequisites: Install the NVIDIA Container Toolkit on the host.
docker run --rm \
--gpus=all \
-p 11434:11434 -p 3000:3000 \
-v ollama_models:/root/.ollama \
ollama-ui:latest
Limit GPU visibility (e.g. only GPU 0):
docker run --rm --gpus 'device=0' -p 11434:11434 -p 3000:3000 ollama-ui:latest
Docker Compose Example (docker-compose.yml at repo root):
services:
ollama-ui:
image: ollama-ui:latest
build: .
ports:
- "11434:11434"
- "3000:3000"
volumes:
- ollama_models:/root/.ollama
deploy:
resources:
reservations:
devices:
- capabilities: [gpu]
environment:
- OLLAMA_HOST=http://localhost:11434
volumes:
ollama_models:
Apple Silicon (Metal) Metal acceleration is available natively when running Ollama directly on macOS. Docker GPU passthrough for Metal is not currently supported in the same way; prefer running Ollama on the host and pointing the container UI to it:
docker run --rm -e OLLAMA_HOST=http://host.docker.internal:11434 -p 3000:3000 ollama-ui:latest
AMD ROCm
If your base image / host supports ROCm and ollama/ollama adds ROCm builds in future, you would expose the devices similarly (e.g. --device=/dev/dri); consult upstream Ollama documentation.
Verify GPU usage after starting:
docker exec -it <container> ollama ps
Or on host: nvidia-smi (NVIDIA) while a model runs.
11. Troubleshooting 🕵️♂️
| Symptom | Cause | Fix |
|---|---|---|
| Installed list empty | Wrong host / unreachable Ollama | Set correct host; test curl <host>/api/tags |
| Pull stuck at 0% | Upstream not streaming completed/total yet |
Wait; incomplete events still appear in log |
| Host not persisting | Cookies blocked | Allow site cookies or set via env variable |
12. Roadmap / Ideas 🗺️
- Persist catalog search & expansion state (localStorage)
- Per-variant progress indicator (when layers known)
- Multi-pull queue (sequential)
- Download speed & ETA estimation
- Keyboard shortcuts (focus search, abort pull)
13. Contributing 🤝
- Fork & clone
- Create a branch:
feat/my-feature - Run
pnpm devand implement - Ensure lint passes:
pnpm lint - Open PR with description & screenshots
14. License 📜
Distributed under the MIT License. See the LICENSE file for full text.
15. At A Glance 👀
| Stack | Key Tools |
|---|---|
| Framework | Next.js App Router (Edge + Node runtime) |
| Data | React Query, NDJSON streaming |
| State | Zustand |
| Styling | Tailwind CSS v4, theme-adaptive glass design system, motion via Framer Motion |
| Backend Integrations | Ollama HTTP API |
| Scraping | Python (httpx, BeautifulSoup, tenacity) |
🚀 Happy hacking! Pull, explore, iterate. 🦙
16. Disclaimer / Infos
⚡️ Disclaimer: Vibe Coding & Copilot ⚡️
🚀 This app was created exclusively through Vibe Coding – basically just as a test of GPT-5 via GitHub Copilot.
🤖 The code is more or less unreviewed, spontaneous, and full of AI magic.
🐛 If you find bugs, feel free to keep them or just continue developing with the vibe.
⚠️ Use at your own risk – but with maximum fun! 🎉
🔧 Docker Native Module Challenge: better-sqlite3 ⚡️
The Challenge: Native module better-sqlite3 failed in Docker with "invalid ELF header" error
The Problem: Architecture mismatch between build environment (macOS ARM64) and runtime (Linux ARM64)
Failed Solutions: Standard pnpm rebuild, copying pre-built modules, multi-stage builds
✅ The Solution: Manual runtime compilation using node-gyp with full build dependencies
🤖 AI Collaboration: Problem solved through iterative debugging with Claude 3.5 Sonnet
⚠️ Note: Unfortunately, GPT-4 and GPT-5 couldn't solve this complex native module compilation issue
Key Learning: Native modules require careful architecture-specific compilation in containerized environments 🐋
# The winning approach: Manual node-gyp compilation at runtime
RUN cd /app/node_modules/.pnpm/better-sqlite3@*/node_modules/better-sqlite3 && \
npm install node-gyp -g && \
node-gyp configure --module_name=better_sqlite3 --module_path=./build && \
node-gyp build
Update: better-sqlite3 was later dropped entirely (briefly replaced with plain JSON files,
then with Node's own built-in node:sqlite module,
available from Node 22.5+). Since it ships inside the Node binary itself, there's no native
addon to compile per platform anymore — the whole class of problem above no longer applies.
17. Release Notes
See the latest changes and release notes here
Install OllamaUI on Unraid in a few clicks.
Find OllamaUI in Community Apps on your Unraid server, review the template, and click Install. Unraid handles the Docker app or plugin setup from the published template.
Requirements
Categories
Related apps
Explore more like this
Explore allDetails
ghcr.io/chrizzo84/ollamauiRuntime arguments
- Web UI
http://[IP]:[PORT:3000]- Network
bridge- Shell
sh- Privileged
- false
- Extra Params
--gpus all
Template configuration
Port for the Web UI
- Target
- 3000
- Default
- 3000
- Value
- 3000
Port for the Ollama API
- Target
- 11434
- Default
- 11434
- Value
- 11434
Storage location for the downloaded Ollama models.
- Target
- /root/.ollama
- Default
- /mnt/user/appdata/OllamaUI/ollama_models
- Value
- /mnt/user/appdata/OllamaUI/ollama_models
Storage location for the user interface database. (Corrected path)
- Target
- /app/data
- Default
- /mnt/user/appdata/OllamaUI/ui_database
- Value
- /mnt/user/appdata/OllamaUI/ui_database
User ID to run the container as. Matches Unraid's 'nobody' user.
- Default
- 99
- Value
- 99
Group ID to run the container as. Matches Unraid's 'users' group.
- Default
- 100
- Value
- 100