Everything not listed in this document should behave the same as upstream Navidrome. If a feature, setting, or behavior is not mentioned here, the upstream documentation is accurate and fully applicable — see the Documentation section of
instructions.mdfor links.
Navidrome is a self-hosted music streaming server with a web player and a Subsonic-compatible API. The one structural difference on StartOS is that Navidrome never owns the music library: /music is assembled from read-only mounts of another installed service's data.
- Upstream repo: https://github.com/navidrome/navidrome
- Wrapper repo: https://github.com/Start9-Community/navidrome-startos
- Image and Container Runtime
- Volume and Data Layout
- File Models
- Dependencies
- Network Access and Interfaces
- Installation and First-Run Flow
- Actions
- Tasks
- Health Checks
- Backups and Restore
- Limitations and Differences
- Quick Reference for AI Consumers
The upstream image, unmodified, running its own entrypoint.
| Property | Value |
|---|---|
| Image | deluan/navidrome |
| Architectures | x86_64, aarch64 |
| Command | sdk.useEntrypoint() — the Navidrome binary |
| Subcontainer | Purpose |
|---|---|
navidrome-sub |
The navidrome daemon — the one to attach to |
The image has no bundled init system, so runAsInit is not used.
One volume for Navidrome's own state, plus a music tree that belongs to somebody else.
| Path | Source | Contents |
|---|---|---|
/data |
main volume |
SQLite database, cache, generated navidrome.toml, store.json |
/music |
dependency volumes (read-only) | One subfolder per selected source (/music/nextexplorer, /music/filebrowser, /music/nextcloud) |
/music is not a StartOS volume of this package. Each selected source is mounted scoped to a user-chosen subfolder of that dependency's volume, and Navidrome scans /music recursively as one library, so multiple sources appear as sibling folders.
The sources differ in what a subfolder path is relative to, which is the single most common setup mistake:
- NextExplorer mounts its
datavolume at/mntin its own container and shows each of its immediate subdirectories as a drive, so the path starts with the drive name —Files/Musicfor the default drive. - FileBrowser Quantum mounts its
datavolume 1:1 at/srvin its own container, so the path is relative to its storage root directly —Music. - Nextcloud mounts its
nextcloudvolume at its webroot, not its data folder, so the path must start withdata/, then the Nextcloud username, thenfiles/—data/admin/files/Music. A bare<username>/files/…path is missing thedata/prefix; the bind mount then fails withmount exited with exit status: 32, because StartOS creates the mount target but never the source.
One model, store.json, holding StartOS-side state only. Navidrome's own navidrome.toml is generated by the application and this package neither reads nor writes it — every setting this package controls is delivered as an environment variable instead.
| Model | File | Seeded by | Rewritten by |
|---|---|---|---|
store.json |
/data/store.json |
.catch() defaults on first read |
Select Music Sources and Configure Navidrome only |
Nothing re-asserts a key behind the user's back: the two actions are the only writers, and main.ts reads the model reactively, so a save restarts the daemon with the new values. A hand edit of store.json survives and takes effect for the same reason — but there is no reason to make one, since both actions cover every field.
The environment variables built from it are consumed by Navidrome only at launch, so they are not a live configuration surface. ND_MUSICFOLDER, ND_RECENTLYADDEDBYMODTIME, ND_JELLYFIN_ENABLED and ND_LOGLEVEL are always set; ND_SCANNER_SCHEDULE and ND_SESSIONTIMEOUT are set only when their field is non-blank, and ND_LISTENBRAINZ_ENABLED/ND_LISTENBRAINZ_BASEURL only when scrobbling is on and Multi-Scrobbler's bridge address resolves. An unresolvable dependency drops both variables rather than substituting an address, so Navidrome falls back to its own defaults instead of pointing at a dead endpoint.
Four, all optional, and all declared conditionally — a dependency this package does not currently need is not declared at all, so its card does not appear.
| Dependency | Kind | Declared when | Mount |
|---|---|---|---|
nextexplorer |
exists |
selected in Select Music Sources | data volume → /music/nextexplorer, read-only |
filebrowser |
exists |
selected in Select Music Sources | data volume → /music/filebrowser, read-only |
nextcloud |
exists |
selected in Select Music Sources | nextcloud volume → /music/nextcloud, read-only |
multi-scrobbler |
running |
Scrobble to Multi-Scrobbler is on | none — resolved by bridge address |
The music sources are exists rather than running because a bind mount reads the volume off disk and never talks to the service. Multi-Scrobbler is running with its own health check, because Navidrome submits scrobbles to it over HTTP.
At least one music source must be selected before the daemon will start.
Two interfaces on one port — Navidrome serves its Subsonic API alongside the player — plus a third when the Jellyfin API setting is on.
| Interface | Id | Type | Port | Purpose |
|---|---|---|---|---|
| Web Interface | ui |
ui | 4533 | Browser access to the Navidrome player |
| Subsonic API | api |
api | 4533 | URL to paste into Subsonic-compatible client apps |
| Jellyfin API | jellyfin |
api | 4533 | /jellyfin path; only exported when Jellyfin API (experimental) is enabled |
All are exported from the same ui MultiHost origin. They are split into two interfaces so a user can copy a dedicated URL into a client app without it also being the one they bookmark for the browser.
Client note: Symfonium's Jellyfin sync currently reports "0 tracks" and never completes against this endpoint — reported upstream (navidrome/navidrome). Upstream lists Finamp, Feishin, and Jellify as tested clients for this API; we haven't independently verified those.
Setup order is the one thing this package constrains, and it runs in the opposite direction from most: the library has to exist before Navidrome will start.
A critical task holds the service until Select Music Sources names at least one installed source and a subfolder within it. Only then can the daemon start — main.ts throws if store.json still has no source, since /music would be empty.
Navidrome's own first-run signup screen is used unchanged. This package generates and stores no credential; the admin account is created by the user on first visit to the web interface.
Three, all user-facing. What follows is what the OS metadata cannot carry.
Run it at install, and again whenever the library moves or a source is added. It rewrites store.json and restarts the daemon; nothing on disk is touched. Idempotent and safe to repeat.
Before saving, the handler mounts each selected subfolder into a throwaway subcontainer to confirm it exists — a nonexistent path fails the bind mount outright. That turns a typo into an immediate validation error rather than a daemon that starts and then dies. The cost is one short-lived container per selected source, a second or two.
Deliberately limited to settings with no equivalent in Navidrome's own admin UI; themes, transcoding, users, and playlists all stay upstream-managed. Saving restarts the daemon, so a change costs an interruption of a few seconds. Idempotent.
Enabling Scrobble to Multi-Scrobbler is not sufficient on its own — a matching token must be entered by hand in both applications' own UIs, as instructions.md describes. Symptom of skipping it: scrobbles are submitted and silently rejected.
For migrating from another Navidrome instance instead of rescanning. Runs only while stopped, since it replaces the SQLite file underneath the daemon, and it is destructive on the current database with no undo. Repeating it simply replaces the database again.
It writes the uploaded file to /data/navidrome.db and deletes any stale navidrome.db-wal / navidrome.db-shm, so SQLite cannot replay a WAL log belonging to the previous database. Cost scales with the file; a large library's database is tens of megabytes.
The imported database records each track by its exact scanned path, so it only lines up if the subfolders configured in Select Music Sources match the source instance's. A mismatch is not destructive — tracks show as missing until a rescan.
One task, raised at install and on any init pass that finds no music source configured.
| Task | Severity | Raised when | Cleared by |
|---|---|---|---|
| "Select where your music library is stored" | critical |
store.json has no mediaSources |
Select Music Sources saving a valid source |
Being critical, it suspends the ordinary Start/Stop controls until it is satisfied — which is the intended behavior, since the daemon cannot start without a library anyway. It can return: clearing every source in a later run raises it again on the next init.
One check, on the only daemon.
| Check | Displayed | Method |
|---|---|---|
navidrome |
"Web Interface" | Port 4533 is listening |
It is a port check, not an HTTP check, so it reports ready as soon as Navidrome binds — before the first library scan finishes. A green check with an empty library is therefore normal on a fresh install; watch the Logs tab for scan progress. A check that never goes green means the daemon died at startup, and the usual cause is a music source whose mount failed or a malformed ND_* value.
The main volume is copied wholesale — sdk.Backups.ofVolumes('main'). That captures the database, the cache, and store.json, so the media-source selection and every setting survive a restore.
The music library is not backed up, because it is not this package's data. Restoring Navidrome onto a server whose NextExplorer, FileBrowser Quantum or Nextcloud has not also been restored gives you a database full of tracks whose files are missing; restore the source service first. No custom restoreInit logic runs beyond the SDK default.
- The library must live in NextExplorer, FileBrowser Quantum or Nextcloud. A StartOS package cannot mount an arbitrary host path, so there is no way to point Navidrome at anything else.
/musicis read-only, so Navidrome cannot write embedded tags, rename files, or fix permissions — matching upstream's own read-only-mount recommendation. Manage the files from the source service.- Navidrome's multi-library feature is not configured here. Every mounted source lands in the single default library as sibling folders. Additional libraries can still be added from Navidrome's own Settings → Libraries.
package_id: navidrome
image: deluan/navidrome
architectures:
- x86_64
- aarch64
subcontainers:
- navidrome-sub # the only container
volumes:
main: /data # /music is dependency-mounted, not a volume of this package
file_models:
- /data/store.json
startos_managed_env_vars:
- ND_MUSICFOLDER
- ND_LISTENBRAINZ_ENABLED
- ND_LISTENBRAINZ_BASEURL
- ND_RECENTLYADDEDBYMODTIME
- ND_LOGLEVEL
- ND_SCANNER_SCHEDULE
- ND_SESSIONTIMEOUT
dependencies:
- nextexplorer # optional, exists
- filebrowser # optional, exists
- nextcloud # optional, exists
- multi-scrobbler # optional, running
interfaces:
ui: { type: ui, port: 4533 }
api: { type: api, port: 4533 }
jellyfin: { type: api, port: 4533 } # only when the Jellyfin API setting is on
actions:
- media-sources
- settings
- import-database
tasks:
- { action: media-sources, severity: critical }
health_checks:
- navidrome # displayed "Web Interface"