Skip to content

Installation — Raspberry Pi

ModelArchitectureDocker platformStatus
Raspberry Pi 5aarch64linux/arm64✅ Recommended
Raspberry Pi 4aarch64linux/arm64✅ Recommended
Raspberry Pi 3 B+armv7llinux/arm/v7⚠️ Best for 1–2 speakers
Raspberry Pi Zero 2 Waarch64linux/arm64⚠️ Limited RAM

The one-liner installer is the fastest path:

Terminal window
curl -sSL https://raw.githubusercontent.com/trudenboy/sendspin-bt-bridge/main/deployment/raspberry-pi/install.sh | bash

It 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.

  1. 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 bluetoothd masked):

      Terminal window
      sudo apt update && sudo apt install -y bluez bluez-tools rfkill
      sudo rfkill unblock bluetooth
      sudo systemctl enable --now bluetooth
    • pair the Bluetooth speaker on the host with bluetoothctl

  2. Run the pre-flight check

    Terminal window
    curl -sSL https://raw.githubusercontent.com/trudenboy/sendspin-bt-bridge/main/deployment/raspberry-pi/check.sh | bash
  3. Create a project directory

    Terminal window
    mkdir -p ~/sendspin-bt-bridge && cd ~/sendspin-bt-bridge
  4. Create .env

    AUDIO_UID=1000
    TZ=Europe/London
    WEB_PORT=8080
    BASE_LISTEN_PORT=8928
  5. Download the current Compose file

    Terminal window
    curl -sSL https://raw.githubusercontent.com/trudenboy/sendspin-bt-bridge/main/docker-compose.yml -o docker-compose.yml
    mkdir -p config
    docker compose up -d
  6. Open the web UI

    http://<raspberry-pi-ip>:<WEB_PORT>
  • WEB_PORT changes the direct web UI/API listener on the Pi.
  • BASE_LISTEN_PORT changes the default Sendspin port block for speakers.
  • Devices without an explicit listen_port use BASE_LISTEN_PORT + device_index.
  • You can override a single device with listen_port and listen_host in 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
Terminal window
docker logs -f sendspin-client
curl -s http://localhost:${WEB_PORT:-8080}/api/preflight | python3 -m json.tool
Terminal window
cd ~/sendspin-bt-bridge
docker compose pull
docker compose up -d

Prefer 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:

Terminal window
nmcli connection modify "<your-wifi-name>" wifi.band a
nmcli 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:

Terminal window
sudo rfkill unblock bluetooth
sudo systemctl enable --now bluetooth

Verify:

Terminal window
rfkill list bluetooth # expect: Soft blocked: no / Hard blocked: no
systemctl is-active bluetooth # expect: active
bluetoothctl show # expect: Powered: yes

If 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:

Terminal window
sudo apt update && sudo apt full-upgrade -y
sudo apt install --reinstall bluez bluez-firmware
sudo systemctl restart bluetooth

If that doesn’t clear it, a full purge + reinstall has been reported to work where --reinstall alone did not (context: issue #151):

Terminal window
sudo apt-get --purge remove bluez bluez-firmware
sudo apt-get install bluez bluez-firmware
sudo systemctl restart bluetooth

Preflight 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.

On the host:

Terminal window
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/.env

Inside the container:

Terminal window
docker exec sendspin-client id
docker exec sendspin-client pactl info 2>&1 | head -3

Red flags:

  • docker info says rootless is enabled, or “Effective UID: 1000”
  • AUDIO_UID is missing from .env, or differs from id -u on the host
  • docker exec … id shows uid=0(root) while the host audio user is uid=1000
  • pactl info inside the container prints Connection refused or Access denied

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.

Terminal window
echo "AUDIO_UID=$(id -u)" >> ~/sendspin-bt-bridge/.env
docker compose -f ~/sendspin-bt-bridge/docker-compose.yml up -d --force-recreate

Then 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: host is 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.