SETUP · UNRAID
Two supported paths. The easy one: install the one-click all-in-one container from Community Applications. The flexible one: run the full Compose stack via Compose Manager Plus, which gives you separate containers, your own reverse proxy, and an optional heavy-TTS sidecar. Both finish in the same browser wizard, and start to on-air takes about five minutes.
One-click (Community Applications): the all-in-one image bundles the whole stack into a single container behind one port. It's the easiest path, and the right one for most people. Full Compose stack (Compose Manager Plus): the maintained docker-compose.yml run as separate broadcast, controller, web, and Caddy services. Pick it if you want isolated containers, your own proxy, or the heavy-TTS sidecar.
OPTION 1 — ONE-CLICK
SUB/WAVE is live in Community Applications. The Apps catalogue is one container per template, so the subwave-aio image bundles icecast2 + liquidsoap, the controller, the web UI, and a Caddy edge together. These are the same images the Compose stack uses, just packaged into one.
Apps tab → search SUB/WAVE → Install, then set:
7700)./mnt/user/appdata/subwave, on the array or pool (not the flash).http://YOUR-UNRAID-IP:7700.SUB/WAVE's state grows over time: hourly archives, the library cache, rendered voices. Point Appdata at /mnt/user/appdata/subwave on your pool or array, never /boot/….
If your appdata share lives on a pool (cache), prefer the direct pool path — e.g. /mnt/cache/appdata/subwave instead of /mnt/user/appdata/subwave. The library cache is a SQLite database, and /mnt/userpaths route through Unraid's shfs/FUSE layer, which SQLite performs poorly over — sluggish admin pages and a ballooning library.db-wal file. Already installed? Stop the container, edit the path mapping to the /mnt/cache/… equivalent (same data), and start it again.
open http://YOUR-UNRAID-IP:7700/onboardingSign in with the ADMIN_USER / ADMIN_PASS you set, and the wizard collects the rest: Navidrome, the LLM provider, TTS, and the DJ persona. The player lives at http://YOUR-UNRAID-IP:7700.
To run a build before it propagates into the Apps catalogue, say you're testing a new tag, add it directly: Docker tab → Add Container → paste this into Template URL, then fill the same fields.
https://raw.githubusercontent.com/perminder-klair/subwave/main/templates/subwave.xmlOPTION 2 — FULL COMPOSE STACK
Run the maintained docker-compose.ymlas separate services. It's a good fit for isolated containers, your own Traefik/SWAG/NPM in front, or the optional Chatterbox/PocketTTS sidecar.
On the Apps tab, search Compose Manager Plus (by mstrhakr) and install the stable release. It adds a Compose section to the Dockertab. You'll also want Docker enabled and the array (or a pool) started so there's somewhere for appdata to live.
Docker tab → Compose → Add New Stack → name it subwave → Edit Stack.
:7700). The optional tts-heavysidecar is profile-gated and won't start, so the DJ falls back to the built-in Piper voice.# Required — the three boot keys
ADMIN_USER=admin
ADMIN_PASS=replace-me-with-a-strong-string # openssl rand -hex 16
SITE_URL=http://YOUR-UNRAID-IP:7700
# Unraid-specific — keep state OFF the flash drive
STATE_DIR=/mnt/user/appdata/subwave/state
CADDY_PORT=7700
TZ=Europe/LondonCompose Manager's project directory lives on the USB flash (/boot/…), so the compose default of ./statewould write SUB/WAVE's growing state (hourly archives, the library cache, rendered voices) onto the boot stick. Set STATE_DIR to an absolute appdata path on your pool or array (/mnt/user/appdata/subwave/state), and Docker creates it on first start.
From the stack's action menu pick Pull & Up (not plain Compose Up), then flip the stack's Autostart → ON so it survives reboots.
The compose file carries build: blocks so a source checkout can rebuild locally. The Unraid project directory has no source, so a plain up tries to build and fails. Pull & Upfetches the prebuilt images from GHCR first, then starts them. If you'd rather, delete the build: blocks and plain up works too.
open http://YOUR-UNRAID-IP:7700/onboardingSame wizard as the one-click path: Navidrome, the LLM provider, TTS, and the DJ persona.
Applies to both options. SUB/WAVE ships a first-class “Ollama — local/cloud”provider. Most Unraid boxes don't have a big GPU, so the nicest path is Ollama's cloud models, which offload inference. Even a low-power box like an Intel N95 handles them fine:
ollama container from the Apps tab (defaults are right: port 11434, appdata /mnt/user/appdata/ollama).ollama signin; approve the printed link in your browser (cloud models need a subscription).http://host.docker.internal:11434, and a :cloud model tag such as glm-5.2:cloud. Or pick a small local tag like llama3.2:3b to run on CPU.ACOUSTIC ANALYSIS — LEAN VS HEAVY
Applies to both options. Tempo, key, intro detection, and loudness run out of the box, the default image analyses them in the background, so just run admin → Library → Rescan (tick re-analyse) and let it churn. The two heavy dimensions, “sounds-like” audio embeddings (CLAP) and vocal ranges(Demucs), need a CPU-torch stack that isn't in the lean image, so they're a separate -heavy build (~1.9 GB, amd64-only). Only switch if you specifically want them.
The heavy/lean split is baked into the all-in-one image, so you switch by editing the container's image, not a setting. ANALYZER_HEAVY does nothinghere; it's the split-stack toggle.
ghcr.io/perminder-klair/subwave-aio:latest to ghcr.io/perminder-klair/subwave-aio-heavy:latest.Your state is untouched, config, personas, library tags, and the cached model weights all live under the appdata volume, so the swap is safe and reversible (edit the field back to subwave-aio to return). First boot on heavy downloads the CLAP/Demucs weights, so give it a few minutes.
On Option 2 the analyzer is its own container, so flip it from the .env:
ANALYZER_HEAVY=1Save, then Pull & Up, and the analyzer container re-pulls as subwave-analyzer-heavy. On an arm64 box you'd also need DOCKER_DEFAULT_PLATFORM=linux/amd64 (emulated).
BEHIND YOUR OWN PROXY
Most Unraid boxes already run a reverse proxy, NPM / SWAG / Traefik / Caddy, for TLS and a tidy hostname. Putting SUB/WAVE behind yours is the common path, and it's a single upstream, not a pile of per-path rules. This applies to both options above.
The one-click AIO image (and the Compose stack's bundled Caddy) already does the same-origin routing internally: / → web UI, /api/* → controller, /stream.mp3→ the Icecast stream, all on the one host port. So your front proxy points at a single target, with no separate backends and no per-path forwarding.
http://YOUR-UNRAID-IP:7700Once a hostname fronts the box, set SITE_URL to the public https:// address, not the IP:port. It backs share cards and absolute links, so it has to be the address listeners actually use. TLS terminates at your proxy; SUB/WAVE speaks plain HTTP behind it, exactly as the bundled Caddy does behind Cloudflare in the reference setup.
SITE_URL=https://radio.example.comTurn response buffering off for /stream.mp3. The bundled Caddy serves the stream unbuffered (flush_interval -1). A front proxy that buffers, and NPM buffers by default, holds the live audio back: latency and stutter, or stalled playback outright. Exempt the stream path and leave everything else on the proxy's normal settings.
Nginx Proxy Manager: open the proxy host → Advancedtab and add a location block for the stream. The rest of the site keeps NPM's normal proxying from the main tab.
location /stream.mp3 {
proxy_pass http://YOUR-UNRAID-IP:7700;
proxy_buffering off;
proxy_cache off;
proxy_read_timeout 1h; # the stream never ends, don't time it out
}The same one knob in the other proxies:
proxy_buffering off; plus a long proxy_read_timeout in a location /stream.mp3 block.reverse_proxy … { flush_interval -1 } on the stream path.If you'd rather your proxy talk to each service directly, run the split-container stack (Option 2) with docker-compose.byo.yml. There web / controller / broadcast bind host ports themselves (7700 / 7701 / 7702), but the web image is still baked for same-origin /api + /stream.mp3, so your proxy then has to replicate the whole route table on one hostname. That's more proxy config, not less, and only worth it if you specifically want the bundled Caddy out of the path. For most people the single-upstream setup above is the easier win.
/, /api, and /stream.mp3 on the single host port. Want TLS and a hostname behind SWAG / NPM / Traefik? See Putting it behind your reverse proxy above: one upstream, plus the one stream-buffering gotcha./mnt/user/appdata/subwave.WHAT'S NEXT
The station is on the air. When a new version lands, head to Updates & Help for the update workflow and a troubleshooting checklist.