services: searxng: image: searxng/searxng:latest container_name: searxng restart: unless-stopped # ------------------------------------------------------------------ # Port binding — 9996 on all interfaces. # Change to "127.0.0.1:9996:8080" to restrict to localhost only. # Traefik handles public routing and TLS via the labels below. # ------------------------------------------------------------------ ports: - 9996:8080 # ------------------------------------------------------------------ # Volumes # Config: settings.yml bind-mounted read-only into the container. volumes: - /opt/docker/conf/searxng/searxng-settings.yml:/etc/searxng/settings.yml:ro # ------------------------------------------------------------------ # Environment — see https://docs.searxng.org/admin/settings/index.html # SEARXNG_SECRET — required for cryptographic signing (cookies, etc.) # BASE_URL — public URL SearXNG reports in pages/RSS/OPDS # INSTANCE_NAME — shown in the page title / footer # ------------------------------------------------------------------ environment: - SEARXNG_SECRET=${SEARXNG_SECRET} # searxng.ana.internal, not the old searxng.pfi.local (migrated # 2026-08-19). `.local` is reserved for mDNS, so the old name was a # standards collision that happened to work; `.internal` is ICANN- # reserved for exactly this. The name is served by the fleet's AdGuard # resolvers from dns/internal.yaml — see scripts/dns-sync.py. - BASE_URL=https://searxng.ana.internal/ - INSTANCE_NAME=SearXNG # ------------------------------------------------------------------ # Resource limits — tune for VM 102's available RAM/CPU # ------------------------------------------------------------------ deploy: resources: limits: memory: 512M cpus: "1.0" reservations: memory: 128M # ------------------------------------------------------------------ # Health check — SearXNG /healthz is the canonical liveness probe. # # ⚠️ `--tries=1` MUST keep its `=1`. This read `- --tries` / `- --spider` # as two separate argv entries until 2026-08-18, and in that form wget # consumed `--spider` as the VALUE of `--tries` — so spider mode never # engaged and every probe DOWNLOADED the response to a file instead of # just checking it. By the time it was caught the container's working # directory held 295,287 `healthz.N` files, one per probe since April, # and wget had to scan all of them to pick the next free filename. That # scan is what intermittently blew the 10s timeout and made the card on # the dashboard flap UNHEALTHY while the service itself was fine. It was # self-worsening: every probe made the next one slower. # # The junk lived in the container's writable layer (the only volume here # is the read-only settings mount), so recreating the container cleared # it. Symptom to watch for if this regresses: `docker exec searxng ls | # wc -l` climbing, and health log entries reading # "Health check exceeded timeout (10s)". # ------------------------------------------------------------------ healthcheck: test: - CMD - wget - --no-verbose - --tries=1 - --spider - http://localhost:8080/healthz interval: 30s timeout: 10s retries: 3 start_period: 15s networks: - tnet labels: # Traefik configuration — auto-discovery via Docker provider - traefik.enable=true # Both names during the migration: `.internal` is the real one now, and # the old `.pfi.local` is kept as a fallback so anything still pointing # at it (a bookmark, a hardcoded config elsewhere) does not break the # day the name changes. Drop the second Host() once nothing uses it — # the Traefik access log will tell you when that is. - traefik.http.routers.searxng.rule=Host(`searxng.ana.internal`) || Host(`searxng.pfi.local`) - traefik.http.routers.searxng.entrypoints=websecure - traefik.http.routers.searxng.tls=true - traefik.http.routers.searxng.service=searxng - traefik.http.services.searxng.loadbalancer.server.port=8080 - homepage.group=Daily - homepage.name=SearXNG - homepage.icon=si-searxng - homepage.description=Privacy-respecting meta-search - homepage.href=http://10.250.50.70:9996 networks: tnet: name: traefik-net external: true