Navidrome -- Setup
Self-hosted music streaming server with OpenSubsonic API, paired with Yubal for YouTube Music library sync. Runs as a Docker Compose stack on a dedicated Debian LXC (132). No SSO -- mobile apps authenticate via Subsonic API tokens. Music stored on unohana.
Links
- GitHub: https://github.com/navidrome/navidrome
- Website: https://www.navidrome.org
- Docs: https://www.navidrome.org/docs
- API (OpenSubsonic): https://opensubsonic.netlify.app
- Yubal GitHub: https://github.com/guillevc/yubal
Infrastructure
| Host | LXC ID | Internal | External | CPU | RAM | Disk |
|---|---|---|---|---|---|---|
| Debian LXC | 132 | 192.168.1.132:4533 | https://music.eva-00.network | 2 cores | 2 GiB | 4 GiB |
Observability
Logs
Container logs are collected via Docker log driver. Query in Loki:
| Query | Purpose |
|---|---|
{container_name="navidrome"} |
Navidrome server logs |
{container_name="yubal"} |
Yubal YouTube Music sync logs |
{container_name=~"navidrome\|yubal"} |
All music stack logs |
{container_name="navidrome"} \|= "error" |
Navidrome errors |
{job="navidrome"} |
All containers on this LXC |
Access: Grafana > Explore > Loki > Enter query
IaC
| Artifact | Path |
|---|---|
| Playbook | ansible/playbooks/navidrome.yml |
| Workflow | .forgejo/workflows/navidrome.yml |
| Docker Compose | services/navidrome/docker-compose.yml |
| Navidrome config | services/navidrome/navidrome.toml |
| Caddy entry | services/caddy/Caddyfile > music.eva-00.network |
| Glance entry | services/glance/glance.yml > Media section + monitor |
| Custom image | homelab/navidrome-custom repo on Forgejo (Dockerfile + themes) |
The playbook manages the full lifecycle:
- Provisions LXC 132 on Proxmox (via create-lxc.yml)
- Installs Docker
- Fetches secrets from Vault
- Deploys Navidrome + Yubal via docker-compose
- Health checks Navidrome at
/ping - Deploys Alloy for log shipping
Vault Secrets
| Path | Key | Purpose |
|---|---|---|
secret/navidrome |
admin_password |
Navidrome admin (holo) password |
secret/navidrome |
milton_password |
Navidrome user (milton) password |
secret/navidrome |
lastfm_api_key |
Last.fm API key for scrobbling + metadata |
secret/navidrome |
lastfm_secret |
Last.fm API secret |
Storage
Music files are stored on unohana (sdc, 4TB) and bind-mounted into LXC 132:
| Host Path | LXC Path | Access | Purpose |
|---|---|---|---|
/mnt/all-might/music |
/music |
Read-write | Music library (shared with Yubal) |
Host directory must be owned by 101000:101000 (maps to Yubal's UID 1000 inside the unprivileged LXC). The playbook handles this automatically.
Navidrome reads music from /music (read-only in container). Yubal writes downloaded music to /music (read-write). Navidrome rescans every 15 minutes.
Navidrome's own data (SQLite DB, cover art cache, metadata) is stored at /opt/navidrome/data/ in the LXC rootfs.
Authentication
No SSO. Navidrome rejected OIDC support (GitHub #858). Subsonic API authentication (used by all mobile apps) requires username/password tokens that bypass reverse proxy auth.
- Web UI: Access via
https://music.eva-00.network, log in with Navidrome credentials - Mobile apps: Connect using Subsonic API at
https://music.eva-00.networkwith username/password - Security: Caddy reverse proxy provides TLS. Access restricted via network (NetBird VPN for remote)
Users
The playbook automatically creates two users on first deploy:
| Username | Role | Purpose |
|---|---|---|
holo |
Admin | Primary admin account |
milton |
User | Regular user account |
Passwords are stored in Vault at secret/navidrome.
Containers
Navidrome
| Setting | Value |
|---|---|
| Image | git.eva-00.network/homelab/navidrome-custom:latest |
| Port | 4533 |
| Data | /opt/navidrome/data |
| Config | /opt/navidrome/navidrome.toml |
| Music | /music (read-only bind mount from unohana) |
| Scan schedule | Every 15 minutes |
| Scrobbling | Last.fm + ListenBrainz |
| Metadata | Last.fm, Spotify, Deezer (artist bios, cover art) |
Yubal
| Setting | Value |
|---|---|
| Image | ghcr.io/guillevc/yubal:latest |
| Port | 8000 |
| Config | /opt/yubal/config |
| Output | /music (read-write bind mount from unohana) |
| Sync schedule | Every 6 hours (0 */6 * * *) |
| Audio format | Opus |
Yubal bridges YouTube Music with Navidrome:
- Downloads albums/playlists from YouTube Music via ytmusicapi + yt-dlp
- Tags files with metadata and downloads synced
.lrclyrics - Organizes into
Artist/Year-Album/Trackfolder structure - Scheduled sync keeps subscribed playlists up to date
- Browser extension (Firefox/Chrome) for one-click downloads
Custom Image & Themes
Navidrome themes are Material-UI JavaScript objects compiled into the React bundle -- there is no runtime theme injection. To add custom themes, we build a custom Docker image from upstream source with additional themes injected.
Repository: homelab/navidrome-custom on Forgejo
How it works
- Dockerfile clones upstream Navidrome source
- Custom theme files (in
themes/) are copied intoui/src/themes/custom/ - Themes are registered in
ui/src/themes/index.jsviased - Full build: Node.js (UI bundle) + Go (binary with embedded UI)
- Image pushed to Forgejo container registry at
git.eva-00.network/homelab/navidrome-custom:latest
Custom themes
| Theme | Type | Description |
|---|---|---|
| AMusic Light Blue | Light | AMusic theme converted to light mode with Apple blue (#007AFF) |
| Apple Music | Light | Clean light theme imitating the Apple Music desktop app |
Updating
The build workflow runs on push to homelab/navidrome-custom main branch. To rebuild with a specific upstream version:
# Dispatch workflow with version input
# e.g. navidrome_version=0.61.2 (or "latest" for HEAD)
To add more themes: add .js and .css.js files to themes/, the Dockerfile auto-registers them.
Build infrastructure
The image builds on the Forgejo runner (LXC 101). Docker was enabled by adding nesting support to the LXC and bumping memory to 4GB for the Go + Node.js compilation.
iOS Clients
Connect any Subsonic-compatible iOS app to https://music.eva-00.network:
| App | Price | CarPlay | Gapless | Offline |
|---|---|---|---|---|
| play:Sub | $4.99 | Yes | Yes | Yes |
| Arpeggi | Free (beta) | Yes | Yes | Yes |
| Narjo | Free (beta) | Yes | Yes | Yes |
| Amperfy | Free | Yes | No (FLAC) | Yes |
Scrobbling
Last.fm
- Register API key at https://www.last.fm/api/account/create
- Store in Vault:
vault-writeworkflow with pathnavidrome, data{"lastfm_api_key": "...", "lastfm_secret": "..."} - Redeploy to pick up new env vars
- Link Last.fm account in Navidrome web UI: Settings > Last.fm
ListenBrainz
- Create account at https://listenbrainz.org
- Get user token from ListenBrainz settings
- Add in Navidrome web UI: Settings > ListenBrainz
Backup
- LXC rootfs (Navidrome DB, playlists, favorites, cover art cache, Yubal config): PBS automatic backup
- Music files on unohana: Part of sazabi 18TB offline rsync (user-managed)
- Vault secrets: Backed up with Vault storage backend