skip to content
walterra.dev
Table of Contents

After getting Qwen3.6-27B running through vLLM on the GX10, the next question was: can this replace the Claude app on my phone?

The OOTB and UX experience might not be the same, but the combination of Open WebUI, Tailscale, and a small always-on box gets surprisingly close. The Spark/GX10 keeps doing what it is good at — serving the model — while my Synology NAS could handle the boring service plumbing.

The setup ended up is like this:

iPhone / iPad
-> Tailscale
-> Open WebUI on Synology
-> vLLM OpenAI-compatible endpoint on GX10
Open WebUI
-> SearXNG on Synology for web search

The important part is what is not in that diagram: no router port forward to the vLLM server. The raw model endpoint stays on the private LAN. Tailscale provides the remote access path, and Open WebUI is the only thing I actually use from the phone.

Why the Synology sits in the middle

The Spark/GX10 can run Open WebUI too, but I like keeping the inference box focused. The vLLM container is already a serious workload, and after the whole unified-memory lockout from the previous post I don’t want random service experiments on the same machine unless they need the GPU.

The Synology DS925+ is perfect for this kind of thing. It is always on, has Docker via Container Manager, already stores my home-server data, and can run small web services without touching the model runtime. So the split is:

  • GX10: expensive tokens, vLLM, Qwen, GPU memory pressure
  • Synology: Open WebUI, SearXNG, Tailscale ingress, persistent chat state
  • iPhone: just a browser/PWA over the tailnet

Tailscale was already installed on the Synology, and I had verified from the NAS that the GX10 vLLM endpoint was reachable. That mattered because Open WebUI runs server-side: the browser talks to Open WebUI, then Open WebUI talks to the model.

Open WebUI on the Synology

I created a small Compose project under the Synology Docker share. Public version, with private addresses replaced:

services:
open-webui:
image: ghcr.io/open-webui/open-webui:main
container_name: open-webui
restart: unless-stopped
ports:
- "8080:8080"
volumes:
- ./data:/app/backend/data
environment:
- OPENAI_API_BASE_URL=http://<gx10-lan-ip>:8001/v1
- OPENAI_API_KEY=aeon-local
- WEBUI_AUTH=true

A couple of small Synology details were worth noting. In non-interactive SSH sessions, /usr/local/bin was not on the path, so I used the full binary paths:

Terminal window
/usr/local/bin/docker --version
/usr/local/bin/docker-compose version

The first docker-compose up -d took a bit because the Open WebUI image is large. Then the app spent a while doing first-run SQLite/Alembic migrations. During that period the container was technically up, but HTTP requests to :8080 reset or failed while the health check still said starting. Waiting was the fix.

Once it was up, I could reach it on the LAN:

http://<synology-lan-ip>:8080

and over Tailscale:

http://<synology-tailnet-name-or-ip>:8080

The first account created in Open WebUI becomes the admin account. After that, the model list showed the vLLM-served model aliases from the GX10.

For iOS, I did not install a random wrapper app. I just used Safari, logged in over Tailscale, and used Add to Home Screen. It behaves enough like a native app for my purposes, and I don’t have to trust a third-party iOS client with my private endpoint, API keys, or chat contents.

Adding private web search with SearXNG

The next useful thing was search. Open WebUI supports web search backends, and SearXNG is the obvious self-hosted option.

I added it to the same Compose project so Open WebUI can reach it by Docker service name:

services:
open-webui:
image: ghcr.io/open-webui/open-webui:main
container_name: open-webui
restart: unless-stopped
ports:
- "8080:8080"
volumes:
- ./data:/app/backend/data
environment:
- OPENAI_API_BASE_URL=http://<gx10-lan-ip>:8001/v1
- OPENAI_API_KEY=aeon-local
- WEBUI_AUTH=true
- ENABLE_RAG_WEB_SEARCH=true
- RAG_WEB_SEARCH_ENGINE=searxng
- SEARXNG_QUERY_URL=http://searxng:8080/search?q=<query>&format=json
depends_on:
- searxng
searxng:
image: searxng/searxng:latest
container_name: searxng
restart: unless-stopped
ports:
- "8081:8080"
volumes:
- ./searxng:/etc/searxng:rw
environment:
- BASE_URL=http://searxng:8080/
- INSTANCE_NAME=private-search

SearXNG also needs a basic settings file. The relevant part for Open WebUI is enabling JSON output:

use_default_settings: true
server:
secret_key: "<generated-secret>"
limiter: false
image_proxy: true
method: "GET"
search:
formats:
- html
- json
ui:
static_use_hash: true

There was a brief shell quoting faceplant while writing the Compose file remotely. The string http://searxng:8080/search?q=<query>&format=json is exactly the kind of thing that punishes sloppy shell heredocs because of <query> and &. Re-running it by piping a script into ssh ... 'bash -s' was much cleaner.

Once SearXNG started, some engines complained in the logs — ahmia, torch, and wikidata had issues, with Wikidata throwing a 403 during initialization. That did not stop the service from working. SearXNG is a meta-search engine; individual backend failures are normal-ish and not necessarily fatal.

The port layout:

Open WebUI container: 8080 -> Synology host 8080
SearXNG container: 8080 -> Synology host 8081

Both containers listen on 8080 internally, but only the host port has to be unique. Docker network namespaces make that fine. Open WebUI talks to SearXNG internally at:

http://searxng:8080/search?q=<query>&format=json

while I can open the SearXNG UI directly at:

http://<synology-lan-ip>:8081
http://<synology-tailnet-name-or-ip>:8081

I initially kept SearXNG unexposed with expose: ["8080"], which is cleaner if only Open WebUI needs it. I later published it on 8081 because I wanted to use the search UI directly too.

The Open WebUI setting that matters

The Compose environment variables are useful, but I still verified the actual Open WebUI admin setting.

In the UI:

Admin Panel -> Settings -> Web Search

The important value is:

SearXNG Query URL:
http://searxng:8080/search?q=<query>&format=json

Not the Synology host URL. Not the Tailscale URL. From Open WebUI’s point of view, SearXNG is another container on the same Compose network, so the Docker service name is the right address.

After saving that, the web-search toggle in chat started working. A search prompt from the phone now goes:

  1. Safari PWA on iOS sends the chat to Open WebUI over Tailscale.
  2. Open WebUI calls SearXNG from inside Docker.
  3. Open WebUI sends the search context to Qwen on the GX10.
  4. vLLM returns the answer.
  5. Open WebUI renders the chat back on the phone.

That is a pretty nice private replacement for the common “chat app with search” workflow.

Security notes

The main rule: do not expose the raw vLLM endpoint to the internet.

The vLLM server is OpenAI-compatible, not internet-hardened. Mine uses a dummy API key because the local server itself is keyless. That is fine on a trusted LAN behind another access layer. It is absolutely not something I want on a public port.

The safer shape is:

  • no router port forwards for GX10 :8001
  • no router port forwards for Open WebUI :8080
  • no router port forwards for SearXNG :8081
  • access from outside the house through Tailscale only
  • keep WEBUI_AUTH=true
  • use Tailscale ACLs if the tailnet grows beyond personal devices

Eventually I may put Caddy or the Synology reverse proxy in front of this for HTTPS even inside the tailnet. For now, Tailscale plus Open WebUI auth is good enough for the first pass.

The actual result

This is the part that feels a little mundane in the best way: I now have a home-screen icon on my iPhone that opens a private chat UI, talks to a 27B model running on the GX10, keeps chat state on the Synology, and can search the web through a SearXNG container.

No cloud model required for the normal path. No subscription meter ticking while I ask random things from the couch. The model is not Claude, and I don’t expect it to be Claude. But as a local, private, mobile-accessible assistant backed by my own hardware, it crossed the threshold from “interesting server demo” into “actually usable home infrastructure”.

Most of the work was just putting the trust boundaries in the right place. The GX10 serves tokens. The Synology serves the app. Tailscale replaces public exposure. Open WebUI gives me a usable mobile interface. SearXNG adds search.

That is a much better shape than trying to make the model server itself be the product.