Navidrome turns a folder of tagged music files into a streaming service that idles around 50MB of RAM, scans a 30,000-track library in a couple of minutes, and speaks the Subsonic API — which gets you polished native clients on every platform. The server takes fifteen minutes to deploy. The real project, and the part nobody warns you about, is your tags: Navidrome trusts metadata completely and your filenames not at all, so a well-tagged library is the whole game.
The server is the easy part
services:
navidrome:
image: deluan/navidrome:latest
ports:
- "4533:4533"
environment:
ND_SCANSCHEDULE: 1h
ND_LOGLEVEL: info
ND_LASTFM_ENABLED: "true"
volumes:
- ./data:/data
- /mnt/music:/music:ro
restart: unless-stopped
Note the :ro on the music mount — Navidrome never writes to your files, which means it can't damage them and you can point it at a library that Syncthing or anything else also manages. First scan of a 50,000-track library takes on the order of 10–20 minutes on a mini PC; after that the hourly incremental scan is seconds. The whole state lives in one SQLite file under ./data, so backup is trivial.
Tags are the database
Navidrome builds its entire model — artists, albums, sort order, artwork — from embedded tags. Three failure modes account for most "my library looks wrong" complaints:
- Missing ALBUMARTIST. Compilations and albums with guest features shatter into a dozen one-track "albums" per featured artist. Set ALBUMARTIST on every file; set COMPILATION=1 on actual various-artists albums.
- Inconsistent DISCNUMBER. Multi-disc albums without disc tags interleave into one scrambled tracklist.
- No embedded art. Navidrome will fall back to a
cover.jpgin the folder, but embedded art at 500–1000px survives file moves and every client displays it.
The tool for the job is MusicBrainz Picard: it fingerprints audio, matches it against the MusicBrainz database, and writes correct tags plus artwork. Budget a weekend for a 10,000-track library you've accumulated haphazardly — cluster, lookup, review, save, about an hour per thousand messy tracks. CLI people should look at beets instead, which does the same job scriptably. Either way, do the tagging before the grand library import, not after; retagging live files just means watching the next scan reshuffle everything.
Clients by platform
The Subsonic API is Navidrome's superpower: a decade of client development predates the server itself, and the current crop is excellent.
| Platform | Pick | Notes |
|---|---|---|
| Android | Symfonium (~$6 one-time) | Best-in-class; offline cache, Android Auto, gapless |
| Android (FOSS) | Tempo | Clean, free, covers the essentials |
| iOS | play:Sub (~$5) or Amperfy (free) | Both do offline sync and CarPlay |
| Desktop | Supersonic or Feishin | Native apps; Feishin is the prettier one |
| Web | Built-in UI | Fine for admin and desk listening |
Transcoding is configured server-side per player: I leave desktop clients on raw FLAC and set phones to Opus at 128kbps, which cuts mobile data by roughly 80% with no audible loss on commuter earbuds. Navidrome shells out to ffmpeg for this on the fly; a mini PC handles several simultaneous transcodes without noticing.
Playlists, including smart ones
Regular M3U playlists import automatically if they sit in the music folder with relative paths. The underused feature is Navidrome smart playlists: JSON files with an .nsp extension dropped in the library, evaluated live. This one is my daily driver — everything played in the last 30 days, newest first:
{
"name": "Recently Played",
"all": [
{"inTheLast": {"lastPlayed": 30}}
],
"sort": "lastPlayed",
"order": "desc",
"limit": 100
}
Rules can combine rating, genre, year, play count, and date added — enough to rebuild most of the algorithmic playlists you actually used on Spotify. "Added this year, played fewer than 3 times" is a better discovery queue than anything an algorithm ever fed me, because everything in it is music I already chose once. The full rule syntax is in the Navidrome docs.
Scrobbling and play counts
Last.fm scrobbling is built in: enable it with the ND_LASTFM_* settings and link your account per-user under personal settings. ListenBrainz — the open-data equivalent — is likewise native, just a user token in your profile. Play counts, ratings, and favourites are also tracked internally per user, which is what the smart playlists feed on, so scrobbles keep accruing even fully offline from third-party services.
What Navidrome doesn't do
It won't acquire, tag, or organise music — it's a pure server over files you already have, which is a feature. Podcasts aren't supported; use a dedicated app. And while Jellyfin plays music perfectly well alongside video, the Subsonic client ecosystem is a full tier above general-purpose media clients for music: gapless playback, proper offline sync, replay gain, Android Auto. If music matters to you, running Navidrome next to Jellyfin costs 50MB of RAM and is worth it — my media box runs both against the same disk.
Bottom line
Deploy the compose file above, spend the weekend with Picard before you import anything, put Symfonium or play:Sub on your phone, and drop one .nsp smart playlist in the library to replace your Spotify heavy rotation. Total cost: hardware you already own plus roughly $6 for a client app, against $12/month forever — break-even inside month one. The catalogue is only what you own, and that's the trade: everything in it is yours, plays offline, and will still be there when the licensing deals change.