Installation — Raspberry Pi
Supported models
Section titled “Supported models”| Model | Architecture | Docker platform | Status |
|---|---|---|---|
| Raspberry Pi 5 | aarch64 | linux/arm64 | ✅ Recommended |
| Raspberry Pi 4 | aarch64 | linux/arm64 | ✅ Recommended |
| Raspberry Pi 3 B+ | armv7l | linux/arm/v7 | ⚠️ Best for 1–2 speakers |
| Raspberry Pi Zero 2 W | aarch64 | linux/arm64 | ⚠️ Limited RAM |
Quick start
Section titled “Quick start”The one-liner installer is the fastest path:
curl -sSL https://raw.githubusercontent.com/trudenboy/sendspin-bt-bridge/main/deployment/raspberry-pi/install.sh | bashIt checks the host, installs Docker if needed, writes a working Compose setup, and can help with Bluetooth pairing.
After installation, the web UI is available on http://<raspberry-pi-ip>:8080 unless you changed WEB_PORT.
Manual installation
Section titled “Manual installation”-
Prepare the host
-
install a current Raspberry Pi OS
-
install Docker
-
install the BlueZ stack and unblock the radio (fresh Raspberry Pi OS Lite, especially Trixie, may ship with Bluetooth soft-blocked or
bluetoothdmasked):Terminal window sudo apt update && sudo apt install -y bluez bluez-tools rfkillsudo rfkill unblock bluetoothsudo systemctl enable --now bluetooth -
pair the Bluetooth speaker on the host with
bluetoothctl
-
-
Run the pre-flight check
Terminal window curl -sSL https://raw.githubusercontent.com/trudenboy/sendspin-bt-bridge/main/deployment/raspberry-pi/check.sh | bash -
Create a project directory
Terminal window mkdir -p ~/sendspin-bt-bridge && cd ~/sendspin-bt-bridge -
Create
.envAUDIO_UID=1000TZ=Europe/LondonWEB_PORT=8080BASE_LISTEN_PORT=8928 -
Download the current Compose file
Terminal window curl -sSL https://raw.githubusercontent.com/trudenboy/sendspin-bt-bridge/main/docker-compose.yml -o docker-compose.ymlmkdir -p configdocker compose up -d -
Open the web UI
http://<raspberry-pi-ip>:<WEB_PORT>
Port planning on Raspberry Pi
Section titled “Port planning on Raspberry Pi”WEB_PORTchanges the direct web UI/API listener on the Pi.BASE_LISTEN_PORTchanges the default Sendspin port block for speakers.- Devices without an explicit
listen_portuseBASE_LISTEN_PORT + device_index. - You can override a single device with
listen_portandlisten_hostin the web UI or/config/config.json.
Example advanced device entry:
{ "mac": "AA:BB:CC:DD:EE:FF", "player_name": "Kitchen Speaker", "listen_port": 8935, "listen_host": "192.168.1.50"}listen_host changes only the advertised host/IP for the player. It does not change the bind address inside the container.
Running more than one bridge on a Pi or LAN segment
Section titled “Running more than one bridge on a Pi or LAN segment”If you run multiple bridge containers or combine a Raspberry Pi bridge with another bridge on the same host/network namespace:
- give each bridge its own
WEB_PORT - give each bridge its own
BASE_LISTEN_PORT - do not assign the same Bluetooth speaker to two running bridges
Verify the runtime
Section titled “Verify the runtime”docker logs -f sendspin-clientcurl -s http://localhost:${WEB_PORT:-8080}/api/preflight | python3 -m json.toolUpdating
Section titled “Updating”cd ~/sendspin-bt-bridgedocker compose pulldocker compose up -dPrefer 5 GHz WiFi when using a USB BT dongle
Section titled “Prefer 5 GHz WiFi when using a USB BT dongle”If you drive multiple speakers from a Pi 4 / Pi 5 with a USB Bluetooth dongle, connect the host to a 5 GHz WiFi network rather than 2.4 GHz. The Pi’s onboard WiFi shares the 2.4 GHz ISM band with Bluetooth, and on a 2.4 GHz link the radios contend on the same air, which can show up as climbing Tx excessive retries in iwconfig, audio dropouts, and BlueZ-induced D-Bus freezes. Switching the connection to 5 GHz typically removes the contention:
nmcli connection modify "<your-wifi-name>" wifi.band anmcli connection up "<your-wifi-name>"If the host has to stay on 2.4 GHz (older router, range constraints), consider wiring it over Ethernet instead. The community report behind this guidance is #212, and the full diagnostic is in Troubleshooting → Audio stuttering and D-Bus freezes on Raspberry Pi.
Bluetooth not working on fresh Raspberry Pi OS Lite
Section titled “Bluetooth not working on fresh Raspberry Pi OS Lite”Some fresh Raspberry Pi OS Lite images — Trixie in particular — ship with Bluetooth soft-blocked by rfkill or without bluetoothd enabled at boot. Symptoms: bluetoothctl cannot find an adapter, the web UI shows no scan results, or pairing fails with “No default controller available”. Run these once before starting the bridge:
sudo rfkill unblock bluetoothsudo systemctl enable --now bluetoothVerify:
rfkill list bluetooth # expect: Soft blocked: no / Hard blocked: nosystemctl is-active bluetooth # expect: activebluetoothctl show # expect: Powered: yesIf bluetoothctl still misbehaves after that — especially on a just-imaged system with no prior pairings — bring the BlueZ stack to a clean state. Try the non-destructive path first:
sudo apt update && sudo apt full-upgrade -ysudo apt install --reinstall bluez bluez-firmwaresudo systemctl restart bluetoothIf that doesn’t clear it, a full purge + reinstall has been reported to work where --reinstall alone did not (context: issue #151):
sudo apt-get --purge remove bluez bluez-firmwaresudo apt-get install bluez bluez-firmwaresudo systemctl restart bluetoothPreflight blocks on step 2: “No Bluetooth controller detected”
Section titled “Preflight blocks on step 2: “No Bluetooth controller detected””You get through install, the speaker pairs and connects fine on the host (bluetoothctl show works, your speaker shows Paired: yes, Connected: yes), but the web UI onboarding stalls at step 2 — Check Bluetooth access with “No Bluetooth controller detected by preflight checks.” Updating to the latest release and power-cycling the adapter doesn’t change anything.
On Raspberry Pi OS specifically, this is almost always a UID / session-owner mismatch, not a Bluetooth problem. The Bluetooth step and the audio step share one root cause — the container process runs as a UID that is not the owner of your user-session PipeWire/PulseAudio socket at /run/user/<uid>/pulse/native, and in the same breath it can lose access to the host Bluetooth D-Bus services. Fix the UID, and both symptoms clear together.
Diagnose
Section titled “Diagnose”On the host:
id -u # your host audio user's UID (usually 1000)docker info 2>/dev/null | grep -iE 'rootless|effective'grep AUDIO_UID ~/sendspin-bt-bridge/.envInside the container:
docker exec sendspin-client iddocker exec sendspin-client pactl info 2>&1 | head -3Red flags:
docker infosaysrootlessis enabled, or “Effective UID: 1000”AUDIO_UIDis missing from.env, or differs fromid -uon the hostdocker exec … idshowsuid=0(root)while the host audio user isuid=1000pactl infoinside the container printsConnection refusedorAccess denied
Fix 1 — make AUDIO_UID match the host
Section titled “Fix 1 — make AUDIO_UID match the host”This is the intended path on rootful Docker. The bridge’s entrypoint reads AUDIO_UID, and auto-drops the bridge process to that UID via gosu so it can reach the user-scoped PipeWire/PulseAudio socket.
echo "AUDIO_UID=$(id -u)" >> ~/sendspin-bt-bridge/.envdocker compose -f ~/sendspin-bt-bridge/docker-compose.yml up -d --force-recreateThen re-run the web UI onboarding.
Fix 2 — rootless Docker: add a global user: override
Section titled “Fix 2 — rootless Docker: add a global user: override”Under Docker rootless mode the auto-drop inside the container is not enough, because the container already runs in a user namespace whose “root” maps to a subordinate UID on the host. Override the compose user: so the container process runs as your host UID directly:
services: sendspin-client: user: "${AUDIO_UID:-1000}:${AUDIO_UID:-1000}"Recreate the container and the onboarding checklist should unblock on the Bluetooth step and the audio step at the same time.
See also the Docker install guide’s troubleshooting user-scoped PipeWire / PulseAudio section for a broader checklist.
network_mode: hostis required for Bluetooth control and Music Assistant auto-discovery.- Raspberry Pi OS Bookworm uses PipeWire with PulseAudio compatibility by default; the bridge works with both PipeWire and PulseAudio.
- Changes to devices, adapters,
WEB_PORT,BASE_LISTEN_PORT, and Music Assistant connection settings require a container restart.