コンテンツにスキップ

Music

I recently (Feb 2026) started the journey of self-hosting my music streaming. The simple answer for why I've gone down this road is "to not pay the big corporations" (e.g. Spotify), but that's more just a convenient lie I've told myself to justify a hobby. So in other words, I set this up so I could spend more money buying music, more effort managing my music library, more time and money keeping my personal infrastructure maintained, and limit my music library. It's so worth it.

All jokes aside, it's actually taught me a lot, about the music industry, copyright, myself (in terms of what I value and what kind of music I really actually enjoy), networking, and containers (the software kind—i.e. Docker/Podman).

Where I'm buying music

For a quick reference these are the current stores from which I buy my music:

Website
Bandcamp
Qobuz
Juno Download

Setup

The basic setup is just Navidrome. I looked around for some other open source projects and found Jellyfin, but I both wanted something specific to music and I saw a lot of people recommended Navidrome over Jellyfin. It's still early days, so I'll probably try both out before completely settling on one. I do also want to check out LMS, but haven't gotten around to it yet.

To access remotely, I've got Tailscale set up on my PC and phone so I don't need to forward any ports or set up a reverse proxy. I'm trying out Symfonium based on the many recommendations I saw on Reddit (though it does cost $5.99 after the free trial). I'm also looking at Yuzic, which is FOSS.

For streaming on my computer, instead of the built-in web page interface that Navidrome considers, I'm trying out Feishin.

Server setup

I set up Navidrome using its Docker image, though I use Podman to run it instead of Docker.

Since I'm on Fedora with SELinux enabled, I did have some issues that I didn't realize I'd encounter—mostly having to do with SELinux labels. Basically, I just had to add a Z to the volume mounting options.

This is the command I used to start the container with Podman:

  podman run -d \
  --name navidrome \
  -v /path/to/music:/music:ro,Z \
  -v /path/to/data:/data:Z \
  -e ND_DATADIR=/data \
  -e ND_MUSICFOLDER=/music \
  -p 4533:4533 \
  deluan/navidrome:latest

Note the ,Z for the /music volume. This one tripped me up at first because I wasn't sure how to format the options after :ro. It's just :ro,Z—a comma-separated list of options (documentation here). For writable mounts, just appending :Z after the container mount point does the trick (since read-write is default). Another random note: it seems like Podman doesn't like trailing / characters at the end of the directory paths (I had this at first and it kept failing).

I also was able to set up a Podman Quadlet so that Navidrome automatically starts up when I log in. I only set it up as a user service, but it should be doable as a root service as well. The Quadlet configuration looks like below:

# navidrome.container

[Container]
ContainerName=navidrome
Image=docker.io/deluan/navidrome:latest
AutoUpdate=registry
NoNewPrivileges=true
PublishPort=4533:4533
Volume=/path/to/music:/music:ro,Z
Volume=/path/to/data/:/data:Z
Environment=ND_DATADIR=/data
Environment=ND_MUSICFOLDER=/music

[Service]
Restart=always

[Install]
WantedBy=default.target

Then install it using podman:

podman quadlet install navidrome.container -r

I have also had issues with putting music into the /path/to/music directory. Again, it seems to be related to SELinux labels. Basically, if I just mv a file into /path/to/music, it doesn't take on the container_file_t label, but if I cp the file in it does. I'm assuming (since I haven't investigated this much) that this has to do with how SELinux contexts are inherited—I'm guessing it's taken from the parent directory on file creation by default, and doesn't change when files are moved around. That would at least explain what I have observed.

The setup for tailscale is pretty easy (assuming Tailscale is already installed on the server and client devices). It's a one-liner to open the server up to the rest of the Tailnet:

tailscale serve 4533

This will run in the foreground, but to get a persistent service to run (even across reboots):

tailscale serve --bg --set-path /music 4533

There's also a cool way to set up paths instead of requiring ports using --set-path. So for Navidrome I use

tailscale serve --bg --set-path /music 4533

This lets me access my Navidrome server from my phone using https://server-hostname.tailnet-name.ts.net/music instead of https://server-hostname.tailnet-name.ts.net:4533, which will be helpful for when I set up more self-hosted services.