Installing Open WebUI Myself — Can You Actually Use a Local LLM Like ChatGPT?
Installing Open WebUI Myself — Can You Actually Use a Local LLM Like ChatGPT?
To cut to the chase: Open WebUI is absolutely worth installing. The one caveat is that the "single-line docker run" you'll find if you search for it should not be copied as-is. Every number in this post comes from running it myself on a machine called jw-ms7c94 running Ubuntu 25.10 — results will vary on other hardware.
The short version first
- Install takes ten minutes. Mine was done in under six, image pull included.
- The UI is genuinely better than a terminal. Markdown, code highlighting, multi-turn conversations, and comparing several models side by side especially.
- But the default docker run command from the post doesn't work as-is. Two settings are missing, and without them the Ollama model list comes back empty.
- It runs on a modest PC too. This machine has 16 cores and 31Gi of RAM, and the container used only 660MiB of memory. The 4.62GB image is the only real burden you'll notice.
1. Environment
| Item | Value |
|---|---|
| Host | jw-ms7c94 (Ubuntu 25.10, kernel 6.17.0-41-generic) |
| CPU / RAM | 16 cores / 31Gi |
| GPU | NVIDIA RTX 3070 8GB |
| Docker | Default bridge network (5 containers already running) |
| Ollama | 0.33.3, GPU recognized correctly, 11 local models |
| Open WebUI | 0.11.4 (image build 2026-09-21) |
The model list included qwen3.8-4b-q6k, supergemma-e4b-q4km-32k, ghunghab/qwen3.8-9b-distill, plus embedding models like nomic-embed-text and bge-m3.
2. Where the blog post is wrong: host.docker.internal doesn't resolve
Method 1 in the post reads like this:
docker run -d -p 3000:8080 \
--add-host=host.docker.internal:host-gateway \
-v open-webui:/app/backend/data \
--name open-webui \
--restart always \
ghcr.io/open-webui/open-webui:main
Run that as-is and the container starts fine. The healthcheck eventually goes healthy. You get HTTP 200.
But the model list comes back empty.
The logs show this one line and nothing else:
ERROR | open_webui.routers.ollama:send_get_request:93 - Connection error: Cannot connect to host host.docker.internal:11434 ssl:default [Name or service not known]
--add-host=host.docker.internal:host-gateway is supposed to plant the name in /etc/hosts, but inside the container it still reports the name as unknown. This is an environment where host-gateway doesn't resolve properly unless you're using --network host. I checked from inside the container:
$ docker exec open-webui curl -s -m 5 http://host.docker.internal:11434/api/tags
Connection timed out after 5000 milliseconds
$ docker exec open-webui curl -s -m 5 http://172.17.0.1:11434/api/tags
Connection timed out after 5000 milliseconds
On top of that, Ollama on this machine was only bound to 127.0.0.1:11434. It was never going to be reachable at 172.17.0.1 (the docker0 gateway) from inside the container. The firewall also had 11434 closed on external interfaces (ufw was active).
So it was name resolution failure + binding restriction + firewall, three layers stacked. The post doesn't mention any of this combination.
The fix: --network host
Eliminating the name lookup entirely was the most reliable approach:
docker run -d --network host \
-e OLLAMA_BASE_URL=http://127.0.0.1:11434 \
-v open-webui:/app/backend/data \
--name open-webui \
--restart always \
ghcr.io/open-webui/open-webui:main
With --network host the container shares the host's network, so 127.0.0.1 points at the host directly. --add-host becomes unnecessary.
The tradeoff is that the port is whatever the host's 8080 is. Since -p gets ignored, check first that nothing else is already using that port.
Port 3000 was taken by another container here, so I started on 3001, then switched to host networking and confirmed 8080 was free before using it.
Watch out: values get stuck in the database
One trap worth knowing. Even after setting OLLAMA_BASE_URL as an environment variable, there are moments when the logs still show host.docker.internal.
That's because the setting is persisted in SQLite. I verified this from inside the container:
$ docker exec open-webui python -c "...select * from config where key like '%ollama%'"
('ollama.base_urls', '["http://host.docker.internal:11434"]', 1791166799)
('rag.ollama.base_url', '"http://host.docker.internal:11434"', 1791166799)
As long as the open-webui volume is alive, that value overrides the environment variable. If you start it correctly from the beginning, no problem — but if you've already launched it wrong once, you need to delete the volume or patch the value directly.
I patched the database directly (the settings screen threw 405 Method Not Allowed):
docker exec open-webui python -c "
import sqlite3
c=sqlite3.connect('/app/backend/data/webui.db')
c.execute('update config set value=? where key=?', ('[\"http://127.0.0.1:11434\"]', 'ollama.base_urls'))
c.execute('update config set value=? where key=?', ('\"http://127.0.0.1:11434\"', 'rag.ollama.base_url'))
c.commit()
"
This one line confirms it's fixed:
curl -s http://127.0.0.1:8080/api/v1/models -H "Authorization: Bearer <token>"
After the fix I got 11 models back, and ERROR log accumulation stopped at one entry (the leftover from before the fix).
3. Sign-up and the first screen
The first person to sign up becomes the admin. It validates email format strictly, though:
$ curl -X POST http://127.0.0.1:8080/api/v1/auths/signup -d '{"email":"jw@local",...}'
HTTP=400
{"detail":"The email format you entered is invalid. Please double-check and make sure you're using a valid email address (e.g., yourname@example.com)."}
I used jw@local since it's a local-only install and got rejected. jw@example.com went through. Even for a local install, you need to follow the domain format.
One thing to watch: setting trusted headers via environment variables blocked sign-in. In a container with WEBUI_AUTH_TRUSTED_EMAIL_HEADER=null, attempting sign-in gives you this:
{"detail":"Your provider has not provided a trusted header. Please contact your administrator for assistance."}
Best to leave that out entirely. Header-based trust is for when you're sitting behind a reverse proxy, and a personal PC doesn't need it.
4. Actual response measurements
I hit /api/chat/completions directly.
Request: {"model":"qwen3.8-4b-q6k:latest","messages":[{"role":"user","content":"2+2=? Reply with just the number"}]}
The response message object had three keys:
keys: ['role', 'content', 'reasoning_content']
content: '4'
reasoning: 'Thinking Process:\n1. The user asks "2+2=?" in Korean.\n...'
Something worth noticing here. reasoning_content comes back as a separate key, and content holds only the final answer. For quiet-reasoning models, that separation carries straight through to the UI. Whether Open WebUI renders the reasoning in a different area from the body text is what determines UI polish, and the API level already guarantees the separation.
A first test made this even clearer. I set max_tokens: 30 with an instruction to "answer in three words" and got an empty string back for content:
reply: ''
usage: {'output_tokens': 3072, ..., 'completion_tokens_details': {'reasoning_tokens': 0}}
All 3072 output tokens got spent with nothing left for content. On models with heavy thinking patterns, a small max_tokens can burn the entire budget on reasoning before reaching the answer. If you want to force a short reply, don't set max_tokens too low. That's a real inconvenience in actual use.
5. Resource measurements
| Item | Measured |
|---|---|
| Image size | 4.62GB |
| First pull | about 6 minutes |
| Container memory (idle) | 660MiB |
| Container memory (processing a response) | 1.037GiB |
/app/backend/data footprint | 1.1G |
vector_db initial size | 188K |
| Healthcheck to healthy | about 40~50 seconds |
| Main page response | 0.027s |
On a 31Gi RAM machine, 660MiB is plenty light. And since ollama uses the GPU here, the CPU side isn't under much load.
The one thing is that the 4.62GB image is a fixed cost you pay up front. Total Docker image size went from 9.708GB to 14.33GB for this, with reclaimable at 9.954GB. If your disk is under 100GB, check capacity before installing.
Even without using RAG, the vector_db directory and SQLite file get created. It sits at 1.1G empty because of the bundled backend files.
6. What the post didn't cover
The Docker Compose route with OLLAMA_BASE_URL=http://ollama:11434. Connecting via a service name on the same network means the --network host problem doesn't arise. If you're running ollama in a container too, this is the safer path. I didn't use it here because ollama was already up as a systemd service, but if both are containers, compose is fundamentally cleaner.
RAG silently stalls if no embedding model is specified. I got past this by luck — nomic-embed-text (274MB) and bge-m3 (1.2GB) were already local. With no embedding model at all, document search fails quietly.
Where the GPU gets picked up. The model is detected automatically. Ollama uses the GPU as-is with no separate -e CUDA_VISIBLE_DEVICES needed.
7. Overall
| Item | Verdict |
|---|---|
| Install difficulty | Easy. But the post's default command needs fixing |
| UI polish | High. Markdown, code blocks, conversation continuity all work |
| Ollama integration | Fails with the default command. Needs --network host or compose |
| Resources | Light on RAM (660MiB), heavy on disk (4.62GB) |
| RAG | Needs an embedding model specified up front |
| Worth running standalone | Yes. Great for hopping between models on one screen |
"No envy for ChatGPT" isn't an exaggeration. Keeping the model list on one screen and moving between models is genuinely convenient in a way a terminal isn't.
That said, if you copy the command this post originally gave you, you'll flounder before getting any of those benefits. --network host is the answer.
In the next post I'll verify RAG and web search integration against real documents. Expect more traps when specifying an embedding model and wiring up SearXNG.
AI Knowledge Hub