
Four VMs, one prompt: the canonical *arr suite for media automation
May 26, 2026
The *arr suite — Sonarr for TV, Radarr for movies, Prowlarr for indexers, Bazarr for subtitles — is the automation backbone of every self-hosted media stack. Sonarr is the eighth-most-deployed app in the 2026 r/selfhosted survey; the rest of the suite is right behind it in install rates.
This post walks through the canonical *arr lab on OpenFactory: four buildable VMs — Sonarr, Radarr, Prowlarr, and Bazarr — each on its own machine, all generated from one prompt and shipped as bootable ISOs.
sonarr (10.77.0.10:8989) — TV PVR pointed at Prowlarr for indexers.radarr (10.77.0.20:7878) — movie PVR, same Prowlarr wiring.prowlarr (10.77.0.30:9696) — one-stop indexer manager that pushes config to Sonarr and Radarr.bazarr (10.77.0.40:6767) — subtitles for both, pre-wired to Sonarr and Radarr.Four Debian Trixie VMs on 10.77.0.0/24. Prowlarr is the indexer hub; Bazarr reaches the two PVRs for library metadata.
Paste this verbatim into the chat builder at console.openfactory.tech. Nothing above or below it — the builder expects the prompt body to start at the “Build a compact multi-node lab…” line.
Build a compact multi-node lab named `arr-media-automation`.
Output discipline: keep the plan small. Use one startup script per node, about 25 shell lines or less. Do not install the real Sonarr, Radarr, Prowlarr, Bazarr, or any *arr / Mono / .NET dependencies at build time. Write deployment-time config examples and tiny Python stdlib or shell compatibility stubs only. The goal is a buildable preparation lab, not a production deployment.
## Topology
Create 4 buildable `debian-trixie` nodes, all `x86_64`, SSH enabled, DHCP/default route intact with lab aliases, firewall disabled, DNS `1.1.1.1` and `8.8.8.8`, user `ops` password `arr-ops` in `sudo`. Every recipe must set top-level `test_config` to `{ "enabled": false, "tests": [] }`.
- `sonarr`: role `tv-pvr`, 2 GB RAM, 16 GB disk, alias `10.77.0.10/24`, x `110`, y `60`
- `radarr`: role `movie-pvr`, 2 GB RAM, 16 GB disk, alias `10.77.0.20/24`, x `350`, y `60`
- `prowlarr`: role `indexer-mgr`, 2 GB RAM, 12 GB disk, alias `10.77.0.30/24`, x `110`, y `220`
- `bazarr`: role `subtitle-mgr`, 2 GB RAM, 12 GB disk, alias `10.77.0.40/24`, x `350`, y `220`
Connections: `sonarr` and `radarr` to `prowlarr:9696`; `bazarr` to `sonarr:8989` and `radarr:7878`.
## Common Recipe Requirements
All nodes: features `headless`, `ssh`; packages `openssh-server`, `python3`, `curl`, `jq`, `iproute2`, `netcat-openbsd`, `ca-certificates`. Each startup script adds the alias with `IFACE=$(ip route show default | awk '{print $5; exit}')`, `ip link set "$IFACE" up || true`, and `ip addr add <alias> dev "$IFACE" || true`. If `os.startup_scripts[].after` is present, it must be the string `"network-online.target"`, not an array.
## Node Requirements
All four nodes share the same compatibility-service shape with different ports and identity payloads. Each registers a Python stdlib service named `<app>-compat.service`.
`sonarr` (`0.0.0.0:8989`): `GET /ping` -> `200 {"status":"OK","appName":"Sonarr"}`; `GET /api/v3/system/status` -> `200 {"appName":"Sonarr","version":"compat-1.0","instanceName":"Sonarr-lab"}`; `GET /metrics` -> `sonarr_compat_up 1`. Env file `/etc/sonarr/sonarr.env` with `PORT=8989`, `PROWLARR_URL=http://10.77.0.30:9696`.
`radarr` (`0.0.0.0:7878`): same shape, `appName == "Radarr"`. Env `/etc/radarr/radarr.env` with `PORT=7878`, `PROWLARR_URL=http://10.77.0.30:9696`.
`prowlarr` (`0.0.0.0:9696`): `GET /ping` -> `200 {"status":"OK","appName":"Prowlarr"}`; `GET /api/v1/system/status` -> `200 {"appName":"Prowlarr","version":"compat-1.0"}`; `GET /metrics` -> `prowlarr_compat_up 1`. Env `/etc/prowlarr/prowlarr.env` with `PORT=9696`.
`bazarr` (`0.0.0.0:6767`): `GET /api/system/health` -> `200 {"app":"Bazarr","status":"OK"}`; `GET /api/system/status` -> `200 {"app":"Bazarr","version":"compat-1.0"}`; `GET /metrics` -> `bazarr_compat_up 1`. Env `/etc/bazarr/bazarr.env` with `PORT=6767`, `SONARR_URL=http://10.77.0.10:8989`, `RADARR_URL=http://10.77.0.20:7878`.
## Scenario
Emit exactly one group scenario named `arr-media-automation-validation`. Put `custom_tests[].assertions[]` inside the scenario entry; leave `scenarios[].tests` empty. Every assertion needs `on_vm`. Use only `port_listening`, `command_output`, and `http_responds`; do not emit `vm_boots`, `network_reachable`, or `service_running`.
- `Stack ports listen`: `port_listening` for `sonarr:8989`, `radarr:7878`, `prowlarr:9696`, `bazarr:6767`.
- `Sonarr identity`: on `sonarr`, `curl -fsS http://localhost:8989/api/v3/system/status | jq -e '.appName == "Sonarr"' >/dev/null && echo sonarr-ok`.
- `Radarr identity`: on `radarr`, `curl -fsS http://localhost:7878/api/v3/system/status | jq -e '.appName == "Radarr"' >/dev/null && echo radarr-ok`.
- `Prowlarr identity`: on `prowlarr`, `curl -fsS http://localhost:9696/api/v1/system/status | jq -e '.appName == "Prowlarr"' >/dev/null && echo prowlarr-ok`.
- `Bazarr health`: on `bazarr`, `curl -fsS http://localhost:6767/api/system/health | jq -e '.status == "OK"' >/dev/null && echo bazarr-ok`.
- `Cross-app reachability`: on `sonarr`, `nc -z -w 5 10.77.0.30 9696 && echo prowlarr-reachable`; on `bazarr`, `nc -z -w 5 10.77.0.10 8989 && nc -z -w 5 10.77.0.20 7878 && echo arrs-reachable`.
Preserve warnings that real Sonarr/Radarr/Prowlarr/Bazarr application binaries, API key rotation, download-client integration (qBittorrent / SABnzbd / NZBGet), indexer credentials, quality profile design, library import path mappings, off-host backups, and `10.77.0.0/24` aliasing are deployment-time concerns.Driving OpenFactory from an AI agent instead of the browser? The same flow is exposed through the OpenFactory MCP server — submit the prompt programmatically, get the build-plan preview back, and call create_build / start_vm on the resulting recipes. Single-image builds go straight through the openfactory CLI.
The prompt produces a buildable preparation lab — the right topology, the right ports listening, deployment-time config templates dropped in the right places, and tiny compatibility services that prove the wiring works. A few things still sit outside the recipe and need operator attention before this carries real load:
The *arr suite needs a front-end. Pair this lab with the Jellyfin media stack post or the Plex media stack post. And the Enterprise & GxP page covers larger rollouts.
OpenFactory's free flow is for browsing. Persistent VMs, SSH access, snapshots, your own ISO, and fleet deployment live on a paid plan.