Skip to content

Dispatcharr

IPTV playlist/EPG manager in the media namespace, routed through a gluetun VPN sidecar. The kill-switch approach (native sidecar ordering, the memory-limit exception) is copied from downloads/prowlarr. Its gluetun startup probe is now the shape every gluetun sidecar uses (see hardening backlog H-20). This page covers what is specific to dispatcharr.

Why this pod's DNS goes out the tunnel

downloads/prowlarr, downloads/qbittorrent and downloads/sabnzbd all set DNS_KEEP_NAMESERVER: "on", leaving CoreDNS as the resolver and gluetun's own DoT resolver unused. Dispatcharr is the one gluetun sidecar in this repository that sets it off: gluetun owns resolv.conf here, and every lookup this pod makes goes out gluetun's own DoT resolver, inside the WireGuard tunnel.

The reason is specific to the IPTV panel this app talks to. Estate DNS forwards to NextDNS, whose ddns rule sinkholes dynamic-DNS hostnames to 0.0.0.0, and the panel 302s every stream to a rotating No-IP hostname (six such domains in use at one count, the set moving over time). Allowlisting them one at a time is a moving target, and the failure mode when a new one hits the sinkhole is a played-fine, authed-fine, spinner-forever player, which reads exactly like a dead provider rather than a DNS block. Taking this one pod off estate DNS resolves the whole class of hostname permanently. downloads/imgur-proxy also bypasses estate DNS, by a different mechanism (its nginx config, not DNS_KEEP_NAMESERVER; see hardening backlog H-21), so this is not the only exception to estate DNS in the fleet. The OPNsense :53 redirect doesn't see this pod's traffic at all: it matches plaintext port 53 to public destinations, and this is DoT (853) carried inside WireGuard.

The cost is that .cluster.local no longer resolves inside this pod. Dragonfly is the one in-cluster dependency, and it's pinned via hostAliases in app/helmrelease.yaml instead.

Redis: isolated from prowler's queue

Dispatcharr uses its own Dragonfly logical database (REDIS_DB: "3"), separate from prowler's worker on db 0. Before this, prowler's worker was consuming and discarding dispatcharr's EPG/M3U Celery tasks: dispatcharr's uwsgi process spawns its Celery workers through attach-daemon, and that child process does not inherit CELERY_BROKER_URL from the parent's environment, so it always falls back to the URL it builds itself from REDIS_*. That fallback URL is what REDIS_DB actually controls; setting CELERY_BROKER_URL in the ExternalSecret template keeps both code paths pointed at the same database, but by itself does not move the workers off db 0. The fix was confirmed by connection state (CLIENT LIST showing the workers on db=3), not by reading the environment back, since an unused env var would look identical either way.

One VPN device key per pod

gluetun-dispatcharr is its own 1Password item with its own VPN-provider device key, not shared with any other gluetun sidecar. The VPN provider's device keys are single-connection: two pods authenticating with the same key at once flap both tunnels.

Worth checking: BLOCK_MALICIOUS

BLOCK_MALICIOUS is off, with a comment pointing at qdm12/gluetun#2054 (skipping gluetun's roughly 300MB malicious-domain blocklist). The comment this was trimmed from also claimed gluetun's own resolver is "never in the path" for this pod, which was true when downloads/prowlarr had the identical setting, but prowlarr sets DNS_KEEP_NAMESERVER: "on" while dispatcharr sets it off. With gluetun's DoT resolver actually handling every lookup here (see above), that specific justification for leaving malicious-domain blocking off does not carry over, and this may be worth a second look independent of this comment sweep.