From 1f2fd75850d56e7068fd1c38ccab6ad2c7b5de8d Mon Sep 17 00:00:00 2001 From: wrightwells Date: Sat, 11 Apr 2026 23:07:28 +0100 Subject: [PATCH] Add openweb-ui service stack and docs --- services/openweb-ui/README.md | 78 ++++++++++++++++++++++ services/openweb-ui/docker-compose.yml | 89 ++++++++++++++++++++++++++ services/openweb-ui/stack.env.example | 9 +++ 3 files changed, 176 insertions(+) create mode 100644 services/openweb-ui/README.md create mode 100644 services/openweb-ui/docker-compose.yml create mode 100644 services/openweb-ui/stack.env.example diff --git a/services/openweb-ui/README.md b/services/openweb-ui/README.md new file mode 100644 index 00000000..6984272f --- /dev/null +++ b/services/openweb-ui/README.md @@ -0,0 +1,78 @@ +# openweb-ui stack + +This folder defines a Docker Compose stack that runs: + +- **tailscale** as the network boundary and HTTPS entrypoint +- **ollama** for local model serving +- **open-webui** for the UI connected to Ollama + +## Files + +- `docker-compose.yml`: stack definition for all services and Tailscale serve config. +- `stack.env.example`: example environment variables (safe placeholders). + +## How the compose setup works + +### `configs.ts-serve` +Embeds Tailscale Serve JSON as an inline Docker config: + +- Enables HTTPS on TCP 443. +- Publishes `${TS_CERT_DOMAIN}:443`. +- Proxies `/` traffic to `http://127.0.0.1:8080` inside the shared network namespace. +- Explicitly disables Funnel for that domain. + +`$${TS_CERT_DOMAIN}` uses double-dollar escaping so Compose passes `${TS_CERT_DOMAIN}` literally into the config payload. + +### `tailscale` service +- Uses `tailscale/tailscale:latest`. +- Authenticates with `TS_AUTHKEY`. +- Stores state at `/var/lib/tailscale` (mapped to `/mnt/appdata/tailscale/${SERVICE}/state`). +- Loads serve config from `/config/serve.json` via Compose `configs`. +- Requires `/dev/net/tun` and `NET_ADMIN` capability. +- Exposes host ports: + - `3000 -> 8080` + - `11434 -> 11434` +- Healthcheck probes `http://127.0.0.1:41234/healthz`. + +### `ollama` service +- Uses `ollama/ollama:latest`. +- Shares network namespace with Tailscale via `network_mode: service:tailscale`. +- Starts only after Tailscale is healthy. +- Loads variables from `stack.env`. +- Mounts model storage at `/mnt/ai_models/ollama`. +- Requests GPU access with `gpus: all`. + +### `open-webui` service +- Uses `ghcr.io/open-webui/open-webui:main`. +- Shares Tailscale network namespace. +- Depends on `ollama`. +- Uses `stack.env` and points to Ollama at `http://127.0.0.1:11434`. +- Stores WebUI data in `/mnt/appdata/open-webui`. + +## Usage + +1. Copy the env template and fill in real secrets: + +```bash +cp stack.env.example stack.env +``` + +2. Edit `stack.env` with your actual values. + +3. Start the stack: + +```bash +docker compose up -d +``` + +4. Check status: + +```bash +docker compose ps +``` + +## Notes + +- Keep `stack.env` out of git (contains secrets). +- Ensure the host supports GPU passthrough and has `/dev/net/tun` available. +- `WEBUI_AUTH`, admin email, and admin password are set through environment variables in `stack.env`. diff --git a/services/openweb-ui/docker-compose.yml b/services/openweb-ui/docker-compose.yml new file mode 100644 index 00000000..2ed193be --- /dev/null +++ b/services/openweb-ui/docker-compose.yml @@ -0,0 +1,89 @@ +configs: + ts-serve: + content: | + { + "TCP": { + "443": { + "HTTPS": true + } + }, + "Web": { + "$${TS_CERT_DOMAIN}:443": { + "Handlers": { + "/": { + "Proxy": "http://127.0.0.1:8080" + } + } + } + }, + "AllowFunnel": { + "$${TS_CERT_DOMAIN}:443": false + } + } + +services: + tailscale: + image: tailscale/tailscale:latest + container_name: tailscale-${SERVICE} + hostname: ${SERVICE} + environment: + - TS_AUTHKEY=${TS_AUTHKEY} + - TS_STATE_DIR=/var/lib/tailscale + - TS_SERVE_CONFIG=/config/serve.json + - TS_USERSPACE=false + - TS_ENABLE_HEALTH_CHECK=true + - TS_LOCAL_ADDR_PORT=127.0.0.1:41234 + - TS_AUTH_ONCE=true + - TS_ACCEPT_DNS=true + configs: + - source: ts-serve + target: /config/serve.json + volumes: + - /mnt/appdata/tailscale/${SERVICE}/state:/var/lib/tailscale + devices: + - /dev/net/tun:/dev/net/tun + cap_add: + - net_admin + ports: + - "3000:8080" + - "11434:11434" + healthcheck: + test: ["CMD", "wget", "--spider", "-q", "http://127.0.0.1:41234/healthz"] + interval: 1m + timeout: 10s + retries: 3 + start_period: 10s + restart: always + + ollama: + image: ollama/ollama:latest + container_name: ollama-${SERVICE} + network_mode: service:tailscale + depends_on: + tailscale: + condition: service_healthy + env_file: + - stack.env + environment: + - TZ=${TZ} + - OLLAMA_HOST=0.0.0.0:11434 + volumes: + - /mnt/ai_models/ollama:/root/.ollama + gpus: all + restart: unless-stopped + + open-webui: + image: ghcr.io/open-webui/open-webui:main + container_name: open-webui-${SERVICE} + network_mode: service:tailscale + depends_on: + - ollama + env_file: + - stack.env + environment: + - TZ=${TZ} + - OLLAMA_BASE_URL=http://127.0.0.1:11434 + - WEBUI_AUTH=${WEBUI_AUTH} + volumes: + - /mnt/appdata/open-webui:/app/backend/data + restart: unless-stopped diff --git a/services/openweb-ui/stack.env.example b/services/openweb-ui/stack.env.example new file mode 100644 index 00000000..6c615231 --- /dev/null +++ b/services/openweb-ui/stack.env.example @@ -0,0 +1,9 @@ +SERVICE=ai +TS_AUTHKEY=tskey-auth-REPLACE_ME +TS_CERT_DOMAIN=ai.example.ts.net +TZ=Europe/London +OLLAMA_HOST=0.0.0.0:11434 +OLLAMA_BASE_URL=http://ollama:11434 +WEBUI_AUTH=True +WEBUI_ADMIN_EMAIL=admin@example.com +WEBUI_ADMIN_PASSWORD=REPLACE_WITH_STRONG_PASSWORD