SETUP · LINUX
Linux is where SUB/WAVE is meant to live: everything runs native, one host port is exposed, and the restart policy brings the station back after a reboot with no unit file to write. This page is the Quick Start plus the host-side details — Docker Engine, the firewall, SELinux, and where the state directory should sit.
STEP BY STEP
Use Docker's own convenience script or your distro's official Docker repo — not the packaged docker.io + docker-compose pair on older releases. SUB/WAVE needs Compose v2 (the space-separated docker compose) and Docker 20.10+ for the host-gateway mapping the containers use to reach services on the host.
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker "$USER"
newgrp dockerThe group change takes effect on your next login; newgrp docker applies it to the current shell. Verify with docker compose version.
The installer detects linux-x64 or linux-arm64, verifies the published SHA256, and installs to /usr/local/bin (elevating with sudo if it isn't writable).
curl -fsSL https://cli.getsubwave.com | shNo root on this box? Install to a user path instead and make sure it's on your PATH: curl -fsSL https://cli.getsubwave.com | sh -s -- --dir ~/.local/bin
The installer offers to run init for you (install dir, deployment shape, admin credentials, then brings the stack up). Afterwards:
subwave setupNavidrome, the LLM, TTS and the DJ persona, then jingle rendering. If your reverse proxy already fronts this host, choose prod-byo at init time — that's the compose shape without the bundled Caddy, described on Manual Install.
The default stack binds exactly one host port — 7700, Caddy. Let it through whichever firewall you run. Debian and Ubuntu:
sudo ufw allow 7700/tcpFedora and RHEL:
sudo firewall-cmd --add-port=7700/tcp --permanent && sudo firewall-cmd --reloadThen http://SERVER-IP:7700. If a Cloudflare Tunnel or a reverse proxy on this same host is the only way in, choose prod-byo at init time and set BIND_ADDRESS=127.0.0.1 in .env — that shape publishes each service on its own host port and honours the override. The default stack ignores BIND_ADDRESS entirely: its Caddy port is published on every interface, and Docker publishes past ufw, so simply skipping the rule above does not close it.
subwave doctorHOST-SIDE DETAILS
On Fedora, RHEL, Rocky and friends running SELinux in enforcing mode, the state/ bind mount is blocked no matter what the Unix permissions say. The symptom is a controller that boots and then can't write to /var/sub-wave. Relabel it once:
sudo chcon -Rt container_file_t ~/subwave/statestate/library.dbis SQLite in WAL mode, and WAL wants real POSIX locking. An NFS or SMB mount gives you corruption and “database is locked” errors under load. Point STATE_DIR at local storage; the music library itself can live anywhere, since SUB/WAVE reaches it over the Subsonic API rather than the filesystem.
Every service is restart: unless-stopped, so the station comes back with the Docker daemon. All you need is:
sudo systemctl enable dockerSet TZ in .env too (e.g. TZ=Europe/London) — schedule slots fire on the container's clock, which is UTC otherwise.
The stack runs rootless, but the admin Stats panel reads container metrics through a locked-down socket proxy that mounts /var/run/docker.sock. Under rootless Docker the socket lives at $XDG_RUNTIME_DIR/docker.sock instead — either repoint that volume in docker-compose.yml, or drop the docker-socket-proxy service and the controller's DOCKER_HOST line. Everything else is unaffected.
SIZING
ANALYZER_MEM_LIMIT, default 6g; lower it on a constrained host.ANALYZER_HEAVY=1 (CLAP + Demucs) or the tts-heavy voice-cloning sidecar. Note the two opt in differently: the analyzer already runs and ANALYZER_HEAVY=1 only swaps its image, while tts-heavy is profile-gated and does not start at all until you run docker compose --profile tts-heavy up -d (or set COMPOSE_PROFILES=tts-heavy in .env).docker-compose.analyzer-gpu.yml over the default compose file.Broadcast, controller, web, Caddy and the lean analyzer are all published multi-arch, so they run native on arm64 — a Pi 4/5 hosts a station fine. The heavy analyzer and tts-heavy are amd64-only and emulating PyTorch on a Pi isn't worth it: keep analysis lean and use a cloud LLM, or Piper and Kokoro for voice (both ship inside the controller image and run on CPU).
DAY TO DAY
subwave statusStack + now-playing snapshot.
subwave logs broadcastTail one service, or all of them.
subwave updatePull new images, recreate only what changed.
subwave restart controllerBounce a single service.
Prefer plain compose on a server you manage with Ansible or similar? Manual Install uses the same images and the same state/ layout, so you can move between the two. Updates & Help covers upgrades and the troubleshooting checklist.