- Frontend Security Enhancements (Nginx/Entrypoint) - Docker Networking & Binding - Code Bug Fix
17 KiB
audioserver
audioserver is a Docker Compose based home audio stack for a Raspberry Pi
audio server. It combines Mopidy, Snapserver, Icecast, a React control UI,
Bandcamp/Spotify credential helpers, and optional native iOS/Android clients.
The intended deployment target is an always-on Linux host on the same LAN as Snapcast clients and a HiFiBerry BeoCreate DSP device.
What This Stack Provides
- Mopidy music server for local files, Spotify, YouTube, Beets, Iris, MPD, and HTTP JSON-RPC control.
- Snapserver multi-room audio with streams for Mopidy, turntable input, and several TCP sources.
- Icecast FLAC stream for browser and mobile playback outside Snapcast.
- audiocontrol web UI served by Nginx for Mopidy, Snapcast, and BeoCreate DSP control.
- Nginx API proxy so the browser talks to one origin instead of many backend services.
- Turntable capture helper that records from a HiFiBerry ALSA device on the host and writes PCM into Snapserver.
- Now-playing helper service for Snapcast metadata and audio recognition.
- GrooveAudio mobile apps for iOS and Android streaming/control.
Architecture
LAN / browser / mobile app
|
v
frontend container, Nginx :${FRONTEND_PORT:-8180}
|-- React audiocontrol UI
|-- /api/mopidy/* -> mopidy:6680
|-- /ws/mopidy -> mopidy:6680
|-- /api/snapcast/rpc -> snapserver:1780/jsonrpc
|-- /ws/snapcast -> snapserver:1780/jsonrpc
|-- /api/hifiberry/* -> BeoCreate DSP REST API
|-- /api/bandcamp/* -> bandcamp-api:8091
|-- /api/spotify/* -> bandcamp-api:8091
|-- /api/now-playing/* -> host now-playing service :8090
|-- /stream.flac -> icecast:8000/stream.flac
mopidy container
|-- writes 48 kHz / 16-bit / stereo PCM to /audio/mopidy.fifo
|-- mirrors PCM to /audio/cava.fifo and /audio/stream.fifo
stream-relay container
|-- reads /audio/stream.fifo
|-- encodes FLAC with ffmpeg
|-- publishes to icecast:8000/stream.flac
snapserver container
|-- reads /audio/mopidy.fifo
|-- reads /audio/turntable.fifo
|-- connects to configured TCP audio sources
|-- serves Snapcast clients on port 1704
host turntable-capture systemd service
|-- records from HiFiBerry ALSA capture device
|-- writes raw PCM to /audio/turntable.fifo
Repository Layout
.
|-- docker-compose.yml # Main service stack
|-- .env.example # Environment template
|-- frontend/ # Nginx image and proxy template
|-- audiocontrol/ # React/Vite control UI
|-- mopidy/ # Mopidy image and config
|-- snapserver/ # Snapserver image and config
|-- icecast/ # Icecast image and config template
|-- stream-relay/ # FIFO-to-Icecast FLAC relay
|-- bandcamp-api/ # Credential helper microservice
|-- scripts/ # Host helper scripts and systemd units
|-- GrooveAudio (Android)/ # Native Android client
|-- GrooveAudio (iOS)/ # Native iOS client
|-- MOBILE_APP_INSTRUCTIONS.md # Extra mobile/networking notes
`-- IMPLEMENTATION_SUMMARY.md # Historical implementation notes
Prerequisites
Host
- Linux host, preferably Raspberry Pi OS or Debian/Ubuntu.
- Docker Engine and Docker Compose plugin.
git,curl,alsa-utils, andsystemdfor host helper services.- A reachable BeoCreate/HiFiBerry DSP API if you want DSP control from the UI.
- A capture-capable ALSA device if you want turntable input.
Install the common packages on Debian/Raspberry Pi OS:
sudo apt update
sudo apt install -y docker.io docker-compose-plugin git curl alsa-utils
sudo usermod -aG docker "$USER"
Log out and back in after adding your user to the docker group.
Optional Host Packages
The now-playing helper can use Flask, NumPy, CAVA, and SongRec:
sudo apt install -y python3 python3-pip cava
pip3 install -r scripts/requirements.txt
SongRec CLI is optional and must be installed separately if you want acoustic
recognition through snapcast-now-playing.py.
Development Tools
Only needed when developing outside Docker:
- Node.js 20+ for
audiocontrol. - Android Studio for the Android client.
- Xcode for the iOS client.
First-Time Setup
1. Clone The Repository
Choose a stable location. The systemd units in scripts/ expect a fixed path,
so avoid temporary directories.
git clone <repo-url> /home/admin/appdata/audioserver
cd /home/admin/appdata/audioserver
Any path is fine, but if you use a different one, update the WorkingDirectory,
ExecStart, and EnvironmentFile paths in the systemd service files before
installing them.
2. Create Your Environment File
cp .env.example .env
nano .env
At minimum, review these values:
BEOCREATE_HOST=beocreate.local
BEOCREATE_PORT=13141
AUDIO_PIPES_DIR=/opt/audiocontrol/pipes
MUSIC_DIR=/opt/audiocontrol/music
FRONTEND_PORT=8180
SNAPSERVER_HOST=192.168.178.100
HIFIBERRY_CARD=sndrpihifiberry
ICECAST_LISTENER_PASSWORD=groove_listen
Set AUDIOCONTROL_PASSWORD if the web UI should require HTTP Basic Auth:
AUDIOCONTROL_USERNAME=admin
AUDIOCONTROL_PASSWORD=change-me
Leave AUDIOCONTROL_PASSWORD empty to disable Basic Auth.
3. Create Host Directories
Create the default music and FIFO directories:
sudo mkdir -p /opt/audiocontrol/music
sudo bash scripts/init-pipes.sh
If you use a non-default FIFO path, pass it to the script and keep .env
aligned:
sudo bash scripts/init-pipes.sh /srv/audioserver/pipes
The script creates:
mopidy.fifoturntable.fifocava.fifostream.fifo
4. Add Music
Put local music in the path configured by MUSIC_DIR. By default:
sudo mkdir -p /opt/audiocontrol/music
sudo rsync -av /path/to/music/ /opt/audiocontrol/music/
The directory is mounted read-only into the Mopidy container at /music.
5. Build And Start The Stack
docker compose up -d --build
docker compose ps
Watch startup logs:
docker compose logs -f
Open the web UI:
http://<audio-server-host>:8180
Use the port from FRONTEND_PORT if you changed it.
6. Scan The Mopidy Local Library
After adding files to MUSIC_DIR, scan the library:
docker compose exec mopidy mopidy local scan
Restart Mopidy if needed:
docker compose restart mopidy
7. Install Turntable Capture
Turntable capture runs on the host, not in Docker, because it needs ALSA device access.
First check the capture card name:
arecord -l
Set HIFIBERRY_CARD in .env to the matching ALSA card name. Then install the
service:
sudo cp scripts/turntable-capture.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now turntable-capture
Check logs:
journalctl -u turntable-capture -f
If the repo is not located at /home/admin/appdata/audioserver, edit
/etc/systemd/system/turntable-capture.service before enabling it.
8. Optional: Install Now-Playing Service
The frontend proxies /api/now-playing/ to a host service on port 8090.
Install Python dependencies:
pip3 install -r scripts/requirements.txt
Install the service:
sudo cp scripts/snapcast-now-playing.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now snapcast-now-playing
Check logs:
journalctl -u snapcast-now-playing -f
As with the turntable service, update paths in the unit file if the repo lives
somewhere other than /home/admin/appdata/audioserver.
Service Ports
| Port | Service | Bind | Purpose |
|---|---|---|---|
8180 |
frontend | configurable via FRONTEND_PORT |
Web UI, API proxy, /stream.flac proxy |
1704 |
Snapserver | 0.0.0.0 |
Snapcast client audio stream |
1705 |
Snapserver TCP RPC | RPC_BIND, default 127.0.0.1 |
Direct Snapcast JSON-RPC TCP |
1780 |
Snapserver HTTP RPC | RPC_BIND, default 127.0.0.1 |
Direct Snapcast HTTP/WebSocket API |
8000 |
Icecast | STREAM_BIND, default 0.0.0.0 |
Direct FLAC stream server |
6600 |
Mopidy MPD | Docker network only by default | MPD access inside stack |
6680 |
Mopidy HTTP | Docker network only by default | Proxied through frontend |
8090 |
now-playing helper | host service | Proxied through frontend |
Mopidy HTTP is intentionally not exposed on the host in docker-compose.yml.
Use the frontend proxy for browser and mobile control.
Important URLs
Replace <host> with your audio server hostname or IP.
Web UI: http://<host>:8180/
Mopidy JSON-RPC proxy: http://<host>:8180/api/mopidy/rpc
Mopidy WebSocket proxy: ws://<host>:8180/ws/mopidy
Snapcast RPC proxy: http://<host>:8180/api/snapcast/rpc
Snapcast WebSocket: ws://<host>:8180/ws/snapcast
Icecast direct stream: http://<host>:8000/stream.flac
Proxied stream: http://<host>:8180/stream.flac
Environment Reference
| Variable | Default | Description |
|---|---|---|
BEOCREATE_HOST |
beocreate.local |
Hostname/IP for the BeoCreate DSP REST API. |
BEOCREATE_PORT |
13141 |
BeoCreate DSP REST API port. |
AUDIO_PIPES_DIR |
/opt/audiocontrol/pipes |
Host directory containing audio FIFOs. |
MUSIC_DIR |
/opt/audiocontrol/music |
Host music library mounted into Mopidy. |
FRONTEND_PORT |
8180 |
Host port for the Nginx/React frontend. |
AUDIOCONTROL_USERNAME |
admin |
Basic Auth username when auth is enabled. |
AUDIOCONTROL_PASSWORD |
empty | Basic Auth password. Empty disables auth. |
RPC_BIND |
127.0.0.1 |
Host bind address for direct Snapserver RPC ports. |
STREAM_BIND |
0.0.0.0 |
Host bind address for Icecast port 8000. |
TZ |
Europe/Vienna |
Container timezone. |
HIFIBERRY_CARD |
sndrpihifiberry |
ALSA capture card name for turntable input. |
ICECAST_SOURCE_PASSWORD |
groove_source |
Source password used by stream-relay. |
ICECAST_RELAY_PASSWORD |
groove_relay |
Icecast relay password. |
ICECAST_ADMIN_PASSWORD |
groove_admin |
Icecast admin password. |
ICECAST_LISTENER_PASSWORD |
groove_listen |
Listener password used by mobile apps. |
SPOTIFY_CLIENT_ID |
empty | Spotify application client ID for Mopidy Spotify. |
SPOTIFY_CLIENT_SECRET |
empty | Spotify application client secret. |
SNAPSERVER_HOST |
192.168.178.100 |
LAN address of this audio server, used by Snapserver/Iris. |
BEETS_HOST |
192.168.178.100 |
Host running Beets web service. |
TCP_POP_HOST |
192.168.178.95 |
TCP source host for Snapserver stream pop. |
TCP_TP_HOST |
192.168.178.89 |
TCP source host for Snapserver stream tp. |
TCP_FRAMEWORK_HOST |
192.168.178.183 |
TCP source host for Snapserver stream framework. |
Operating The Stack
Start or restart everything:
docker compose up -d --build
Stop everything:
docker compose down
Show service status:
docker compose ps
Follow all logs:
docker compose logs -f
Follow one service:
docker compose logs -f mopidy
docker compose logs -f snapserver
docker compose logs -f frontend
docker compose logs -f icecast
Restart one service:
docker compose restart mopidy
docker compose restart snapserver
docker compose restart frontend
Open a shell in a container:
docker compose exec mopidy bash
docker compose exec frontend sh
Rebuild after config or source changes:
docker compose build
docker compose up -d
Updating
git pull
docker compose pull
docker compose up -d --build
docker compose ps
If .env.example changed, compare it with your .env and copy over any new
variables you need.
Audio Routing
Mopidy To Snapcast
Mopidy writes raw PCM into /audio/mopidy.fifo. Snapserver reads that FIFO as
the mopidy stream:
Mopidy -> /audio/mopidy.fifo -> Snapserver stream "mopidy" -> Snapcast clients
Mopidy also mirrors the same audio into:
/audio/cava.fifofor visualization./audio/stream.fifofor Icecast streaming.
Mopidy To Icecast
The stream-relay container reads /audio/stream.fifo, encodes FLAC with
ffmpeg, and publishes to Icecast:
Mopidy -> /audio/stream.fifo -> stream-relay -> Icecast /stream.flac
The stream is available directly on port 8000 and through the frontend proxy.
Turntable To Snapcast
The host turntable-capture service records from ALSA and writes raw PCM to
the turntable FIFO:
Turntable / HiFiBerry capture -> turntable-capture -> /audio/turntable.fifo
-> Snapserver stream "turntable" -> Snapcast clients
All primary PCM paths use 48 kHz, 16-bit, stereo audio.
Web UI Development
The Docker stack builds the React app automatically from audiocontrol/.
For local frontend development:
cd audiocontrol
npm install
npm run dev
The Vite development server runs on:
http://localhost:5173
Build manually:
npm run build
Preview the production build:
npm run preview
Mobile Apps
The native clients live in:
GrooveAudio (Android)/GrooveAudio (iOS)/
They are designed to use:
- Port
8000for the Icecast FLAC stream. - Port
8180for proxied Mopidy and Snapcast control APIs. ICECAST_LISTENER_PASSWORDfrom.envfor stream authentication.
Default app settings assume:
Host: 192.168.178.100
Password: groove_listen
Change those in the app settings if your server IP or listener password differs.
See MOBILE_APP_INSTRUCTIONS.md for additional notes about mobile and remote
access.
Android
cd "GrooveAudio (Android)"
./gradlew assembleDebug
Or open the folder in Android Studio and run the app on a device.
iOS
Open GrooveAudio (iOS)/GrooveAudio.xcodeproj in Xcode, select a device or
simulator, and run the app.
Spotify And Bandcamp
Spotify
-
Create a Spotify application in the Spotify Developer Dashboard.
-
Put the credentials in
.env:SPOTIFY_CLIENT_ID=... SPOTIFY_CLIENT_SECRET=... -
Recreate Mopidy:
docker compose up -d --build mopidy
The frontend also includes a proxied credential store under /api/spotify/
served by bandcamp-api.
Bandcamp
The bandcamp-api service stores Bandcamp credentials or cookies in the
bandcamp_config Docker volume. The frontend proxies it under
/api/bandcamp/.
Security Notes
- Set
AUDIOCONTROL_PASSWORDbefore exposing the UI outside a trusted LAN. - Keep
RPC_BIND=127.0.0.1unless direct Snapserver RPC access is required. - Prefer the frontend proxy for control APIs instead of exposing backend ports.
- Icecast binds to
0.0.0.0by default throughSTREAM_BIND; restrict it if you do not want direct LAN access to port8000. - Do not commit
.env; it may contain passwords and API credentials.
Troubleshooting
Frontend Does Not Load
Check the frontend container and logs:
docker compose ps frontend
docker compose logs -f frontend
Verify the configured port:
docker compose port frontend 80
Basic Auth Fails
Check AUDIOCONTROL_USERNAME and AUDIOCONTROL_PASSWORD in .env, then
recreate the frontend:
docker compose up -d --force-recreate frontend
BeoCreate DSP Is Unreachable
Confirm the host and port in .env:
set -a
. ./.env
set +a
curl "http://$BEOCREATE_HOST:$BEOCREATE_PORT/api/volume"
If that fails from the host, fix DNS/IP routing before debugging Docker.
No Mopidy Audio In Snapcast
Confirm the FIFOs exist:
ls -l "${AUDIO_PIPES_DIR:-/opt/audiocontrol/pipes}"
Then check service logs:
docker compose logs -f mopidy snapserver
If Mopidy started before Snapserver was ready, restart Mopidy:
docker compose restart mopidy
Local Music Is Missing
Confirm MUSIC_DIR contains music files and rescan:
docker compose exec mopidy mopidy local scan
docker compose restart mopidy
No Turntable Audio
Check ALSA devices:
arecord -l
Update HIFIBERRY_CARD in .env, then restart the service:
sudo systemctl restart turntable-capture
journalctl -u turntable-capture -f
Icecast Stream Is Silent Or Missing
Check Icecast and relay logs:
docker compose logs -f icecast stream-relay
Verify the stream URL:
curl -I http://localhost:8000/stream.flac
curl -I http://localhost:8180/stream.flac
The stream only carries Mopidy audio. If Mopidy is stopped or idle, the stream may be silent.
Snapcast Clients Cannot Connect
Snapcast clients need access to port 1704 on the audio server:
sudo ufw allow 1704/tcp
Confirm Snapserver is listening:
docker compose logs -f snapserver
Now-Playing API Returns Errors
Check the host service:
systemctl status snapcast-now-playing
journalctl -u snapcast-now-playing -f
Make sure the service unit paths match the actual repository path and that
Python dependencies from scripts/requirements.txt are installed.
Backup Notes
Important persistent state lives in:
.envMUSIC_DIR- Docker volume
audioserver_mopidy_data - Docker volume
audioserver_bandcamp_config - Any systemd unit overrides you create
List volumes:
docker volume ls | grep audioserver
Back up named volumes with your normal Docker volume backup process.