Skip to content
josh goble edited this page Aug 16, 2026 · 2 revisions

Wiring

The reason Keelarr exists. Running the containers is easy; connecting them is the work.

What gets connected

Connection From → to
Download client Radarr, Sonarr, Lidarr → SABnzbd or qBittorrent, each with its own category
Applications Prowlarr → Radarr, Sonarr, Lidarr, so indexers sync into them
Indexer proxy Prowlarr → FlareSolverr
Subtitles Bazarr → Radarr, Sonarr
Library folders A root folder in each Arr, created on the host path behind the container path

The stack wiring panel: connections confirmed, and what still needs the operator

Addresses are resolved per source, not globally

This is the part that is easy to get wrong by hand.

"localhost" means something different inside each container. An address that works from your browser may not work from Prowlarr, and an address that works from Prowlarr may not work from Radarr. So Keelarr resolves each connection from the source app's position on the network:

Topology How the address is resolved
Shared user-defined network By container name — Docker's DNS resolves it
Default bridge By IP, because the default bridge carries no DNS
Host networking Via the host address
macvlan By the container's own address on that network
Published port, remapped By the host port, not the container port

When two apps genuinely cannot reach each other — different networks, nothing published — the link is reported as blocked, with the reason, rather than given an address that will silently time out.

The four states, and only one is written

Every possible connection is reconciled into one of four states:

State Meaning Written?
correct Something already there matches what we would configure No — nothing to do
drift Exactly one comparable entry exists, pointing somewhere else No
ambiguous Several comparable entries exist and none match No
absent Nothing comparable exists Yes

Drift is never overwritten. A download client pointing somewhere unexpected is usually a deliberate choice, and silently replacing it would be worse than leaving it. It is reported so you can decide.

Ambiguous is never guessed at. Two candidates and no clear match is not something to resolve by picking one.

Written last, tested first

Every write follows the same sequence:

  1. Read the app's own schema for that integration
  2. Build the payload from that schema, so fields Keelarr does not name keep the app's defaults
  3. Submit it to the app's own test endpoint
  4. Only if the app accepts it, write it

A refusal is reported in the app's own words. That matters because a write that half-happens produces a bare 400 naming a field rather than the actual problem.

After writing, Keelarr runs each app's testall endpoint so the final verdict is the app's opinion, not Keelarr's.

When nothing needs doing

A wiring run that changes nothing is a success, not a failure. It runs automatically after every install, and reporting a red failure over an install that worked is a lie.

But it only says "every connection is already configured" when every link was actually confirmed correct. If two links were merely unreachable, it says that instead — "we wrote nothing" and "everything is correct" are different facts.

Apps refuse for reasons that are not about the payload

Two real examples, both invisible to unit tests and both found on live hosts:

SABnzbd's hostname whitelist

A fresh SABnzbd whitelists only the hostname it sees itself as — inside a container, that is the container ID. It answers 403 to every other name, including its own container name, which is exactly what everything else addresses it by.

http://sabnzbd:8080/api?mode=version    → 403 Forbidden
http://172.25.0.9:8080/api?mode=version → {"version":"5.1.0"}

Keelarr appends the hostname through SABnzbd's own API, addressed by IP — the hostname cannot carry its own fix — and keeps every entry already present, because SABnzbd's set_config replaces the list rather than appending to it.

Download categories

An Arr refuses a download client with no category, and its error names a field rather than the real problem. Keelarr checks the category exists and creates it before attempting the write.

Wired is not the same as working

A stack can be perfectly connected and still not do anything, because it needs something only you have. The check separates these:

  • Connections — what Keelarr manages, and their state
  • Needs you — an indexer, a Usenet account, a Plex token

Prowlarr with no indexers is reported explicitly: nothing in the stack can find releases until at least one exists. That is a finished result, not a half-done one, and the verdict says so.

Leftovers

Removing an app does not remove references to it from apps that were pointed at it. Keelarr detects these — Prowlarr's proxy for a deleted FlareSolverr, its sync to a Lidarr that no longer exists — and reports them. On a live NAS this found an orphaned Lidarr reference nobody knew about.

Running it

Wiring runs automatically after every install and deploy. To run it by hand, use Check Wiring in the toolbar to see the current state, then apply.

The check is strictly read-only, including the testall calls, which exercise each app's existing configuration rather than anything Keelarr proposes.

Clone this wiki locally