Navidrome on PMSS
Navidrome turns a music folder in your Pulsed Media seedbox into a library you open in your browser, and apps that use the Subsonic API can connect to it, all in about 45 MiB of memory when idle (measured with one track). The steps below create its admin before any port opens and switch off its usage reporting and its outside artist lookups from the first start. It runs in rootless Docker inside your account. Tested on a Pulsed Media seedbox on 24 October 2026 with deluan/navidrome 0.64.2: the install, the browser login, a library scan and Subsonic API requests were tested; playing music and connecting a player app were not.
Privacy
Level: LOW with the settings in this guide, MEDIUM with Navidrome's defaults. With its defaults, using Navidrome reduces your privacy: it sends anonymous usage data ("Insights") to insights.navidrome.org, and the server looks up the artists in your library on Last.fm, Deezer and ListenBrainz, so those services see the artist names it looks up. Source: Navidrome's configuration code (conf/configuration.go and consts/consts.go in the Navidrome repository).
Step 1 sets ND_ENABLEINSIGHTSCOLLECTOR=false and ND_ENABLEEXTERNALSERVICES=false, which turn all of these off. In the test, Navidrome's start-up log then said All external integrations are DISABLED! and the login page used its offline background, plain black. Navidrome's database stays in ~/docker/navidrome/data.
Requirements
| Need | Navidrome on PMSS |
|---|---|
| Memory | 44-46 MiB idle in the test, with one track in the library. A large library, a scan or transcoding uses more; that was not measured. userShowLimits shows your account's memory use against its limit. |
| Own login | Yes. The admin is created in step 2, while no port is published. |
| Address | https://SERVER/user-USERNAME/navidrome/ through your own web server. Navidrome's ND_BASEURL setting carries the path, so the proxy fragment needs no map-urlpath.
|
| Other apps | None. Rootless Docker is part of every Pulsed Media seedbox plan. |
Install
Run these over SSH as your own account. Replace USERNAME with your account name, SERVER with the server name in your panel address, and PORT with a free port between 2000 and 38000 that ss -tln does not list. Other accounts on the same server can connect to 127.0.0.1, so Navidrome first runs with no published port, gets its admin, and only then gets its port.
1. Make the folders, a random password and the settings file. Both files are readable only by you.
mkdir -p ~/docker/navidrome/data ~/docker/navidrome/music && chmod 700 ~/docker/navidrome ( umask 077; openssl rand -base64 24 | tr -d '\n=/+' > ~/docker/navidrome/admin.pw printf 'ND_BASEURL=/user-USERNAME/navidrome\nND_ENABLEINSIGHTSCOLLECTOR=false\nND_ENABLEEXTERNALSERVICES=false\n' > ~/docker/navidrome/navidrome.env ) chmod 600 ~/docker/navidrome/admin.pw ~/docker/navidrome/navidrome.env
2. Start it with no published port and create the admin from inside the container. The image is pinned to the digest that was tested. The reply to the admin request contains login tokens, so it goes to /dev/null.
docker run -d --name navidrome --env-file ~/docker/navidrome/navidrome.env \
-v ~/docker/navidrome/data:/data -v ~/docker/navidrome/music:/music:ro \
deluan/navidrome@sha256:38dc2727bfcfd5ede290f8ada114fc90368146f265ae4701ddddbcbe2a44ee52
printf '{"username":"admin","password":"%s"}' "$(cat ~/docker/navidrome/admin.pw)" | \
docker exec -i navidrome wget -qO- --header='Content-Type: application/json' --post-file=- \
http://127.0.0.1:4533/user-USERNAME/navidrome/auth/createAdmin > /dev/null
echo $?
The last line prints 0 when the admin was created. If it prints anything else, Navidrome had not finished starting: wait a few seconds and run the printf command (all three lines) and the echo line again. Do not go on to step 3 until it prints 0, because without an admin anyone who reaches the port can create one. The echo check was added for this guide. admin is the login name. Navidrome accepts this request only once: in the test a second attempt was refused with Cannot create another first admin.
3. Start it again with its port on 127.0.0.1. The admin and settings stay in ~/docker/navidrome/data.
docker rm -f navidrome docker run -d --name navidrome --restart unless-stopped --env-file ~/docker/navidrome/navidrome.env \ -p 127.0.0.1:PORT:4533 -v ~/docker/navidrome/data:/data -v ~/docker/navidrome/music:/music:ro \ deluan/navidrome@sha256:38dc2727bfcfd5ede290f8ada114fc90368146f265ae4701ddddbcbe2a44ee52
4. Add the proxy fragment and check the syntax. Run the check from your home folder.
cat > ~/.lighttpd/custom.d/navidrome.conf << 'EOF'
$HTTP["url"] =~ "^/user-USERNAME/navidrome(/|$)" {
auth.require = ()
setenv.set-request-header = ( "Authorization" => "" )
proxy.server = ( "" => ( ( "host" => "127.0.0.1", "port" => PORT ) ) ),
proxy.header = ( "upgrade" => "enable" )
}
EOF
/usr/sbin/lighttpd -t -f ~/.lighttpd.conf
The last command prints Syntax OK. If it prints an error, fix or delete navidrome.conf at once: a broken fragment keeps your web panel down at the next restart.
What the lines do: auth.require = () lets requests past your panel login to Navidrome's own login form. setenv.set-request-header removes your panel login, which a browser logged into your panel sends with every request, before the request reaches Navidrome; Navidrome signs in with its own form and does not need it. In the test the browser login worked with the panel login on every request. "upgrade" => "enable" lets WebSocket connections through. Your web server usually picks up the file within about a minute (Lighttpd Custom Configuration); the first fragment of an account can take longer, see Troubleshooting.
Configure
- Open
https://SERVER/user-USERNAME/navidrome/and sign in asadminwith the password thatcat ~/docker/navidrome/admin.pwshows. - Your music: the install mounts
~/docker/navidrome/musicread-only as/music. To use the folder where your music already is, change the left side of-v ~/docker/navidrome/music:/music:roin step 3 to that folder, then rundocker rm -f navidromeand step 3 again; read-only is enough. That variant was not part of the test. In the test Navidrome scanned the folder when it started. Restarting it withdocker restart navidromeshould start a new scan; that was not tested. - Apps: Subsonic-compatible apps connect to
https://SERVER/user-USERNAME/navidromewith your Navidrome login. The Subsonic API can send the password in the address asp=; choose an app's token setting instead where it has one. No app was tested.
Integrate
Navidrome reads music from the folder you mount; the test connected it to no other app. A program that saves music into that folder adds to the library at the next scan; not tested.
Test
docker ps --filter name=navidromeshows the container as Up.https://SERVER/user-USERNAME/navidrome/shows Navidrome's login page, and signing in withadminleads to the album list. In the test a request made right after the fragment was added answered 502 once while Navidrome was still starting, then worked.- A request with no login straight to the port is refused, which is what another account on the server sees:
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:PORT/user-USERNAME/navidrome/api/song
In the test this returned 401. The login page itself loads without a login, as it does by design.

Uninstall
docker rm -f navidrome rm ~/.lighttpd/custom.d/navidrome.conf touch ~/.lighttpd/custom
Deleting a fragment changes no remaining file, so touch ~/.lighttpd/custom makes your web server restart without it (deleting or disabling a fragment explains why). docker rmi deluan/navidrome@sha256:38dc2727bfcfd5ede290f8ada114fc90368146f265ae4701ddddbcbe2a44ee52 frees the image's disk space. The test removed the container and the image; removing the fragment and the folder was not tested.
Deleting the data is your choice. rm -rf ~/docker/navidrome deletes the password file, the settings file, Navidrome's database with your users and playlists, and any music kept in ~/docker/navidrome/music. A music folder you mounted from elsewhere is not touched. In the test the files Navidrome wrote belonged to the account, so a plain rm should be enough.
Troubleshooting
- 502 Bad Gateway right after starting: Navidrome is still starting; wait a few seconds. If it stays, see the 502 section of Lighttpd Custom Configuration.
- The address still answers 404 several minutes after you added the fragment: your web server has not picked it up yet; in a test on another Pulsed Media server the first fragment of an account took about 15 minutes. Run
touch ~/.lighttpd/custom. - The library is empty: the mounted folder has no music, or it was empty when Navidrome started. Check the left side of the
/musicmount, then rundocker restart navidrome, which should start a new scan (not tested). - Tracks show as
[Unknown Artist]or[Unknown Album]: the files have no tags. The test track was an untagged file and showed that way. docker pullordocker runfails before Navidrome starts: see the troubleshooting section of the Rootless DOCKER page.
See also
- Self-Hosted Apps on PMSS — other applications for rootless Docker, with their path settings and login defaults
- Lighttpd Custom Configuration — proxy fragments and the panel login
- Rootless DOCKER — running containers in your account
- Seedbox — Pulsed Media seedbox plans, each with rootless Docker included