Create Private Music Library

Create Private Music Library: Self-Hosted Music with Navidrome

I got tired of Spotify subscriptions, algorithmic recommendations I didn't ask for, and worrying about licensing restrictions on my own music collection. Three months ago, I set up Navidrome—a lightweight, self-hosted music server—in Docker on a spare Hetzner VPS. Now I stream my entire music library (15,000+ tracks) across my phone, tablet, and browser with zero monthly fees and complete control. Here's exactly how I did it.

Why Self-Host Your Music?

Before I dive into the setup, let me be clear about the appeal. Streaming services are convenient, but they're also rent-forever models. If Spotify raises prices or removes an artist's catalog, you're stuck. With a private music library, you own the relationship to your music files.

I prefer Navidrome because it's lightweight—it runs on about 256MB RAM—supports Subsonic API (so it's compatible with dozens of mobile clients), and has a modern web UI. Unlike Jellyfin (which is fantastic but heavier), Navidrome is purpose-built for music, not a general media server. And unlike Plex, there's zero phone-home behavior or proprietary nonsense.

The typical use case: you have your music files (FLAC, MP3, etc.) stored locally or on a file server. Navidrome indexes them, makes them searchable, and exposes a clean interface to stream from anywhere you have internet access.

Prerequisites

You'll need:

Tip: If you don't have music files yet, rip your CDs with tools like abcde or MusicBrainz Picard. Many homelabbers use YouTube Music's backup feature or legally source FLAC files from Bandcamp artists.

Setting Up Navidrome with Docker Compose

I'll walk you through the exact Docker Compose setup I use. This assumes you have Docker and Docker Compose installed. If not, run:

curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh
sudo curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose

Create a directory for your Navidrome setup:

mkdir -p ~/navidrome/{music,data}
cd ~/navidrome

Now, create a docker-compose.yml file:

version: '3'
services:
  navidrome:
    image: deluan/navidrome:latest
    container_name: navidrome
    ports:
      - "4533:4533"
    environment:
      ND_LOGLEVEL: info
      ND_BASEURL: ""
      ND_PORT: "4533"
      ND_MUSICFOLDER: "/music"
      ND_AUTOIMPORTPLAYLISTS: "true"
      ND_ENABLESTARRATING: "true"
      ND_DEFAULTLANGUAGE: "en"
    volumes:
      - ./music:/music:ro
      - ./data:/data
    restart: unless-stopped
    networks:
      - caddy_network

networks:
  caddy_network:
    external: true

Before spinning this up, you'll want to copy your music files into the music directory:

cp -r /path/to/your/music/* ~/navidrome/music/
docker-compose up -d

Check that it's running:

docker-compose logs -f navidrome

You should see scan activity. On first run, Navidrome will index your entire music collection. With 15,000 tracks, this took about 2 minutes on my VPS.

Watch out: Ensure the music volume is mounted as read-only (:ro) unless you're using Navidrome's playlist save feature. This prevents accidental writes to your precious music files.

Exposing Navidrome with Caddy

Navidrome listens on port 4533 internally, but you'll want to expose it securely over HTTPS. I prefer Caddy because it handles Let's Encrypt SSL automatically.

If you don't already have Caddy running, here's a minimal setup alongside Navidrome. Create a Caddyfile:

music.yourdomain.com {
    reverse_proxy localhost:4533
}

Add Caddy to your Docker Compose:

version: '3'
services:
  caddy:
    image: caddy:latest
    container_name: caddy
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile
      - caddy_data:/data
      - caddy_config:/config
    networks:
      - caddy_network
    restart: unless-stopped

  navidrome:
    image: deluan/navidrome:latest
    container_name: navidrome
    ports:
      - "4533:4533"
    environment:
      ND_LOGLEVEL: info
      ND_BASEURL: ""
      ND_PORT: "4533"
      ND_MUSICFOLDER: "/music"
      ND_AUTOIMPORTPLAYLISTS: "true"
      ND_ENABLESTARRATING: "true"
    volumes:
      - ./music:/music:ro
      - ./data:/data
    restart: unless-stopped
    networks:
      - caddy_network

networks:
  caddy_network:
    driver: bridge

volumes:
  caddy_data:
  caddy_config:

Update your DNS to point music.yourdomain.com to your VPS IP, then:

docker-compose down
docker-compose up -d
docker-compose logs caddy

Within seconds, Caddy will provision an SSL certificate and your music server is live at https://music.yourdomain.com

Accessing Your Music Library

Open https://music.yourdomain.com in your browser. You'll see a login screen. On first access, use username admin and password admin (change this immediately).

The interface is clean: search, browse by artist/album, create playlists, and see what's playing. The web player works well, but the real power comes from mobile clients.

Mobile clients I recommend:

  • iOS: Subsonic, Stoney, or Navidrome native app (when available)
  • Android: Ultrasonic, Substreamer, or Symfonium

All of these support the Subsonic API, so they'll work with your Navidrome instance. Just point them to https://music.yourdomain.com, enter your credentials, and stream.

Optional: Add Backup and Auto-Updates

Since your music library is precious, consider regular backups. I use a simple cron job to sync metadata:

0 2 * * * rsync -av ~/navidrome/data/ backup-server:/backups/navidrome/ >> /var/log/navidrome-backup.log 2>&1

For automatic image updates, add Watchtower to your compose file:

  watchtower:
    image: containrrr/watchtower
    container_name: watchtower
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    command: --interval 86400
    restart: unless-stopped

Performance Notes

On a budget VPS (like RackNerd's entry-level KVM), Navidrome performs beautifully. Even with 20,000+ tracks and concurrent users, CPU usage stays below 5%. The real bottleneck is bandwidth—streaming FLAC over a slow connection won't work, so transcode on-the-fly if needed. Set ND_TRANSCODINGCACHESIZE to enable this.

For truly massive libraries (50,000+ tracks), consider a more powerful instance or local storage. A Hetzner Dedicated Server with ZFS storage is overkill but a nice option if you have the budget.

Next Steps

Your private music library is now live. From here, you might add:

  • Playlists: Share playlists with family via the web interface
  • Favorites: Star tracks in the web UI or clients; they sync across devices
  • Last.fm scrobbling: Enable in Navidrome settings to track listening habits
  • Additional music sources: Add podcasts or audiobooks by placing them in separate folders

Navidrome is lightweight, stable, and exactly what a music lover needs. No algorithms, no subscription creep, no vendor lock-in. Your music, your rules.

```