Skip to main content

Deploying Navidrome on YunoHost with Docker (and Shared Permissions)

This guide documents the process of setting up Navidrome (an open-source music server) on a YunoHost VPS using Docker. The goal was to achieve a setup where:

  1. The service runs in a secure Docker container.
  2. Music files are stored in a shared directory (/home/shared/Music) accessible by all YunoHost users.
  3. The service sits behind Nginx with a custom domain and SSL.

1. The Challenge: Permissions

The main hurdle when running Docker containers on YunoHost is mapping the user permissions correctly. If you run the container as a default user (like root), you may encounter permission errors when trying to access folders owned by your system users, or vice versa.

Finding the Correct User IDs (UID/GID)

To allow the specific user (kalvin0x58c) to manage the container, and the all_users group to upload music, we first identified the correct IDs on the host system:

# Check the specific user ID
id kalvin0x58c
# Output example: uid=36200(kalvin0x58c) ...

In this deployment:

  • UID (User ID): 36200 (The main admin/user)
  • GID (Group ID): 4002 (The YunoHost all_users group)

2. Directory Setup

We created a shared music directory and configured ownership so that the container can read it, and any user in the all_users group can upload to it via SFTP.

# Create the shared directory
mkdir -p /home/shared/Music

# Set ownership: User 36200 owns it, Group 4002 shares it
chown -R 36200:4002 /home/shared/Music

# Set permissions: Owner/Group have Read+Write, Others have Read
chmod -R 775 /home/shared/Music

3. Docker Configuration (docker-compose.yaml)

We placed the configuration in /opt/navidrome/docker-compose.yaml. Note the specific user: definition which maps the container's internal process to our host IDs.

services:
  navidrome:
    image: deluan/navidrome:latest
    # Syntax is UID:GID. 
    # Using 36200 (User) and 4002 (Shared Group) ensures access to the shared folder.
    user: 36200:4002
    ports:
      - "56896:4533" # Maps internal port 4533 to host 56896
    restart: unless-stopped
    environment:
      ND_LOGLEVEL: info
    volumes:
      - "./data:/data"                  # Database stored locally in /opt/navidrome/data
      - "/home/shared/Music:/music:ro"  # Music folder mounted as Read-Only

4. Nginx Reverse Proxy Configuration

To expose the service at https://navidrome.obulou.org, we created a custom Nginx configuration.

Crucial fixes included:

  • WebSocket Support: Required for the UI to update automatically.
  • Timeout Increases: Preventing timeouts during large initial library scans.
  • Client Body Size: Set to 0 (unlimited) to prevent upload errors if the API is used for uploads.

The Reverse Proxy Block

*File location: /etc/nginx/conf.d/navidrome.obulou.org.d/reverseproxy.conf*

location @reverseproxy--proxy {
  proxy_pass        http://127.0.0.1:56896;
  proxy_redirect    off;
  
  # Standard headers
  proxy_set_header  Host $host;
  proxy_set_header  X-Real-IP $remote_addr;
  proxy_set_header  X-Forwarded-Proto $scheme;
  proxy_set_header  X-Forwarded-For $proxy_add_x_forwarded_for;

  # WebSocket Upgrade Magic
  proxy_http_version 1.1;
  proxy_set_header Upgrade $http_upgrade;
  proxy_set_header Connection $connection_upgrade;

  # Performance & Timeouts
  client_max_body_size 0;        # Allow unlimited file size
  proxy_request_buffering off;   # Stream immediately
  proxy_buffering off;
  proxy_connect_timeout 3600s;   # High timeout for long scans
  proxy_send_timeout 3600s;
  proxy_read_timeout 3600s;
}

location / {
    try_files /dev/null @reverseproxy--proxy;
}

5. Troubleshooting: The "Permission Denied" Crash loop

The Error

After starting the container, the logs showed a restart loop with the following error: FATAL: Error creating cache path: mkdir /data/cache: permission denied

The Cause

The ./data directory in /opt/navidrome was originally created by root during the first test run. When we switched the container to run as user 36200, it lost the ability to write to its own database folder.

The Fix

We manually fixed the ownership of the data directory on the host:

cd /opt/navidrome
chown -R 36200:36200 data
docker compose restart navidrome

Summary

By explicitly defining the user: UID:GID in Docker Compose and ensuring the host directory permissions matched, we achieved a secure, multi-user compatible music server.