Why these apps together
A small team or a household wants an AI chat whose answers can cite current web pages, without sending prompts to a hosted model or queries to a paid search API.
The app pages size and deploy each app alone. Together they need two things neither page covers: Open WebUI must be pointed at SearXNG over the network Caddy shares with both, and SearXNG must be told to answer in JSON, which it refuses by default (403 Forbidden). This page sizes the pair as one server and gives the exact address and setting.
Apps in this stack
| App | Role in the stack | Load level | Compose support |
|---|---|---|---|
| Open WebUI | The chat interface, with Ollama running the model on the CPU. | Light | Run-tested |
| SearXNG | The metasearch engine Open WebUI sends its web searches to. | Light | Supported |
How the apps connect
- Open WebUI to SearXNG:
http://searxng:8080/search - Open WebUI sends web searches to SearXNG's /search endpoint over Caddy's edge network, by service name. The request never leaves the server's Docker networks.
Resources for the whole server
Computed by the Stack Sizer's engine for these apps and load levels: what the apps need, the system baseline and the safety headroom. The same input in the Stack Sizer gives the same numbers.
| Part | vCPU | RAM (GiB) | Disk (GiB) |
|---|---|---|---|
| Open WebUI | 3 | 5.75 | 18.472 |
| SearXNG | 0.5 | 0.5 | 0.6 |
| System baseline | 0.1 | 1.25 | 8.3 |
| Safety headroom | 0.72 | 1.5 | 4.106 |
| Recommended total | 4.32 | 9 | 31.478 |
Caddy is not included in the resource calculation of the Stack Sizer or the Deployment Spec.
Files from the Compose Generator
# Generated by the ANAROQ Compose Generator: https://anaroq.com/tools/compose-generator
# Engine @anaroq/compose 1.0.0, catalog 2026.10.10, exposure caddy.
# Apps: open-webui 1.2.0, searxng 1.0.0 (supported, not run-tested).
# No secrets in this file: copy .env.example to .env and fill it in before `docker compose up -d`.
services:
open-webui:
image: "ghcr.io/open-webui/open-webui:v0.11.4"
restart: "unless-stopped"
environment:
OLLAMA_BASE_URL: "http://open-webui-ollama:11434"
WEBUI_SECRET_KEY: "${OPEN_WEBUI_SECRET_KEY:?set OPEN_WEBUI_SECRET_KEY in .env}"
volumes:
- "open-webui-data:/app/backend/data"
networks:
- "open-webui-internal"
- "edge"
depends_on:
open-webui-ollama:
condition: "service_started"
healthcheck:
start_period: "120s"
open-webui-ollama:
image: "ollama/ollama:0.40.2"
restart: "unless-stopped"
volumes:
- "open-webui-ollama:/root/.ollama"
networks:
- "open-webui-internal"
searxng:
image: "searxng/searxng:2026.10.13-22c64b8d3"
restart: "always"
environment:
SEARXNG_SECRET: "${SEARXNG_SECRET:?set SEARXNG_SECRET in .env}"
SEARXNG_BASE_URL: "https://${SEARXNG_DOMAIN:?set SEARXNG_DOMAIN in .env}/"
volumes:
- "searxng-config:/etc/searxng"
- "searxng-data:/var/cache/searxng"
networks:
- "searxng-internal"
- "edge"
healthcheck:
test:
- "CMD"
- "wget"
- "-q"
- "--spider"
- "http://127.0.0.1:8080/healthz"
interval: "30s"
timeout: "5s"
retries: 3
start_period: "60s"
caddy:
image: "caddy:2.11.7-alpine"
restart: "unless-stopped"
environment:
OPEN_WEBUI_DOMAIN: "${OPEN_WEBUI_DOMAIN:?set OPEN_WEBUI_DOMAIN in .env}"
SEARXNG_DOMAIN: "${SEARXNG_DOMAIN:?set SEARXNG_DOMAIN in .env}"
ports:
- "80:80"
- "443:443"
- "443:443/udp"
volumes:
- "./Caddyfile:/etc/caddy/Caddyfile:ro"
- "caddy-data:/data"
- "caddy-config:/config"
networks:
- "edge"
networks:
open-webui-internal: {}
searxng-internal: {}
edge: {}
volumes:
open-webui-data: {}
open-webui-ollama: {}
searxng-config: {}
searxng-data: {}
caddy-data: {}
caddy-config: {}
# Generated by the ANAROQ Compose Generator: https://anaroq.com/tools/compose-generator
# Engine @anaroq/compose 1.0.0, catalog 2026.10.10, exposure caddy.
# Copy this file to .env and fill in every value. Never commit .env.
# ANAROQ does not create passwords or keys: generate them on your own machine.
# --- open-webui ---
# Key Open WebUI signs sessions with (WEBUI_SECRET_KEY). Changing it signs everyone out.
# Secret, required. For example: openssl rand -hex 32
OPEN_WEBUI_SECRET_KEY=
# Public host name of Open WebUI behind the reverse proxy, for example chat.example.com.
# Required.
OPEN_WEBUI_DOMAIN=
# --- searxng ---
# Server secret_key SearXNG uses for cryptography (SEARXNG_SECRET overrides the value in settings.yml). SearXNG refuses to start with the placeholder ultrasecretkey.
# Secret, required. For example: openssl rand -hex 32
SEARXNG_SECRET=
# Public host name of SearXNG behind the reverse proxy, for example search.example.com. Used for SEARXNG_BASE_URL.
# Required.
SEARXNG_DOMAIN=
# Generated by the ANAROQ Compose Generator: https://anaroq.com/tools/compose-generator
# Engine @anaroq/compose 1.0.0, catalog 2026.10.10, exposure caddy.
# Each site address comes from .env through the Caddy container's environment.
{$OPEN_WEBUI_DOMAIN} {
reverse_proxy open-webui:8080
}
{$SEARXNG_DOMAIN} {
reverse_proxy searxng:8080
}
Generated by the Compose Generator's engine for these apps behind Caddy. The files hold no secrets: copy .env.example to .env and fill it in.
Compose Analyzer result
10 findings: 0 error, 0 high, 0 medium, 10 low, 0 info.
Low10
- LowAQ-HLTH-005Line 8, column 5
services.open-webuiopen-webui does not limit the size of its logs (
logging.options.max-size).Fix: Limit the log size so logs cannot fill the disk.
- LowAQ-SEC-005Line 11, column 7
services.open-webui.environmentopen-webui passes secrets through
environment(WEBUI_SECRET_KEY).Fix: Where the image supports it, use
secrets:or a_FILEvariable instead. - LowAQ-HLTH-002Line 24, column 5
services.open-webui-ollamaopen-webui-ollama has no
healthcheck.Fix: Add a healthcheck, so Compose and you can tell when the service is ready.
- LowAQ-HLTH-005Line 24, column 5
services.open-webui-ollamaopen-webui-ollama does not limit the size of its logs (
logging.options.max-size).Fix: Limit the log size so logs cannot fill the disk.
- LowAQ-HLTH-005Line 31, column 5
services.searxngsearxng does not limit the size of its logs (
logging.options.max-size).Fix: Limit the log size so logs cannot fill the disk.
- LowAQ-SEC-005Line 34, column 7
services.searxng.environmentsearxng passes secrets through
environment(SEARXNG_SECRET).Fix: Where the image supports it, use
secrets:or a_FILEvariable instead. - LowAQ-HLTH-002Line 54, column 5
services.caddycaddy has no
healthcheck.Fix: Add a healthcheck, so Compose and you can tell when the service is ready.
- LowAQ-HLTH-005Line 54, column 5
services.caddycaddy does not limit the size of its logs (
logging.options.max-size).Fix: Limit the log size so logs cannot fill the disk.
- LowAQ-PORT-005Line 60, column 9
services.caddy.ports.0This file publishes 3 port mappings. Docker publishes ports past ufw rules.
Fix: Filter them in the
DOCKER-USERchain or at the provider's firewall, or do not publish them. - LowAQ-VOL-005Line 64, column 9
services.caddy.volumes.0caddy bind-mounts the relative path ./Caddyfile.
Fix: That works, but the data lives in the directory you run Compose from: back it up.
The Compose Analyzer (ruleset 1.0.0) checked docker-compose.yml: 0 error, 0 high, 0 medium and 10 low findings. A recipe is published only without error and high findings.
Continue in the tools
The link opens the Compose Generator with these apps, options and exposure, and with the Deployment Spec of this sizing, so cloud-init and the Runbook start from the same state.
Runbook
Before the first start
- Copy
.env.exampleto.envnext todocker-compose.ymland fill in every value. Compose stops with an error while a required value is missing. - Create OPEN_WEBUI_SECRET_KEY on your own machine, for example with
openssl rand -hex 32. ANAROQ never creates passwords or keys. - Create SEARXNG_SECRET on your own machine, for example with
openssl rand -hex 32. ANAROQ never creates passwords or keys. - Keep
Caddyfilenext todocker-compose.yml; the Caddy container mounts it read-only. - Ports 80 and 443 (TCP, and 443/UDP for HTTP/3) must be reachable from the internet for Caddy's automatic HTTPS.
- Before starting, point the A/AAAA records of the domain in OPEN_WEBUI_DOMAIN (open-webui) to this server; Caddy requests its certificate on first start.
- Before starting, point the A/AAAA records of the domain in SEARXNG_DOMAIN (searxng) to this server; Caddy requests its certificate on first start.
- Models are not downloaded automatically:
docker compose exec open-webui-ollama ollama pull <model>. CPU only, no GPU. - Start the stack with
docker compose up -dand follow it withdocker compose ps.
After the first start
Allow JSON answers in SearXNG
After the first start, add json to search.formats in /etc/searxng/settings.yml (the searxng-config volume), then restart SearXNG. Without it Open WebUI's searches fail with 403 Forbidden.
search: formats: - html - jsonPoint Open WebUI at SearXNG
In Open WebUI, open Settings, then Admin, then Web Search. Turn web search on, choose searxng as the engine and enter the query URL below.
http://searxng:8080/searchCheck the connection
Start a chat, turn on web search for it and ask about something recent. The answer lists the pages SearXNG returned; a 403 means the JSON format is still off.
What the apps share
- Both apps get a public host name through Caddy. SearXNG is not set up as a public instance (its limiter is off), so keep its host name to yourselves or restrict it in the Caddyfile; Open WebUI does not need SearXNG to be public.
- Web search adds the fetched pages to the model's context. On the light level the model's context is small, so keep the number of search results low in Open WebUI's web search settings.
- Back up Open WebUI's data volume and SearXNG's config volume (/etc/searxng), which holds the settings.yml you edit below. SearXNG's cache volume needs no backup.
Alternatives
- Without web search, Open WebUI alone is enough: its own page sizes it and opens it in the Compose Generator. Open WebUI
- Open WebUI can also use a hosted search API instead of SearXNG; that removes the second app but sends every query to that provider.
Basis
- Catalog
- 2026.10.10
- Sizing policy
- 2026.10.0
- Compose engine
- @anaroq/compose 1.0.0
- Analyzer ruleset
- 1.0.0
Sources
How profiles, sizes and Compose files are built is described in the methodology. Read the methodology