A flexible music streaming solution that combines a containerized server (Navidrome) with a browser-accessible virtual desktop for music management. Works on any Linux server - cloud VPS, home lab, or local machine.
Features β’ Quick Start β’ Installation β’ Troubleshooting
- What Is This?
- Architecture
- Features
- Prerequisites
- Quick Start
- Detailed Installation
- Configuration
- Usage Guide
- Troubleshooting
- File Structure
- Advanced Topics
- Credits
Cloud Music Stack turns any Linux server into your personal music streaming platform. Think Spotify, but you own everything.
β
Stream your music collection from anywhere
β
Access a full desktop environment in your browser
β
Automatic HTTPS with zero configuration
β
Compatible with popular mobile apps
β
Full FLAC/lossless audio support
β
No subscription fees or data sharing
- Music enthusiasts who want to own their library
- Audiophiles who demand lossless quality
- Anyone wanting to break free from streaming subscriptions
- Self-hosting enthusiasts looking for a complete solution
This project uses a hybrid approach that elegantly solves the challenge of running GUI applications on headless servers.
1. Host Layer (Your Server's OS)
- XFCE Desktop: Lightweight GUI environment
- TigerVNC Server: Provides the desktop video stream
- noVNC + Websockify: Converts VNC to browser-accessible WebSocket
- SpotiFLAC: GUI application for music acquisition
2. Container Layer (Docker)
- Navidrome: Scans your music folder and serves it via Subsonic API
- Caddy: Reverse proxy that automatically handles SSL certificates
Browser β Caddy (Port 443) β Navidrome (Port 4533) β Music Library
Browser β Caddy (Port 8443) β noVNC (Port 6080) β VNC Desktop
Caddy sits at the edge, routing requests to either your music server or your virtual desktop, both secured with automatic HTTPS.
- πΌ Lossless Streaming - Native FLAC support with no quality loss
- π₯οΈ Browser Desktop - Full Linux GUI accessible from any device
- π Auto SSL - Let's Encrypt certificates provisioned automatically
- πΎ Persistent Storage - Music survives container restarts
- π± Mobile Ready - Works with Amperfy, Symfonium, and more
- π Docker-Based - Easy updates and portable configuration
- π₯ Health Monitoring - Built-in health checks for all services
- βοΈ Environment-Based Config - Easy deployment with
.envfile - β CI/CD Validated - Automated testing ensures configuration quality
Most music servers require you to upload files manually. With CMS, you get a virtual studio - a full desktop environment in your browser where you can download, organize, and manage music directly on the server. Everything stays in one place.
Minimum Specs:
- CPU: 2 cores (1 core works but slower)
- RAM: 2GB (or 1GB + 4GB swap)
- Storage: 20GB for system + your music collection
- OS: Ubuntu 20.04/22.04/24.04 or Debian 10+
Hosting Options:
- Cloud: AWS EC2, Google Cloud, DigitalOcean, Linode, Vultr, Hetzner
- Home: Raspberry Pi 4 (4GB+), old laptop, mini PC, NAS
- Local: Any Linux machine you can access remotely
Your server must have these ports accessible:
| Port | Purpose | Required |
|---|---|---|
22 |
SSH access | Yes |
80 |
HTTP (for SSL verification) | Yes |
443 |
HTTPS (web player) | Yes |
8443 |
HTTPS (virtual desktop) | Yes |
Cloud providers: Configure your security group/firewall
Home servers: Set up port forwarding on your router
-
Domain name - Free options available:
- DuckDNS (easiest, free)
- No-IP
- Afraid.org
-
Basic Linux skills - Comfortable with SSH and command line
-
Docker knowledge (helpful but not required)
For experienced users who want to get running fast:
# 1. Clone and enter directory
git clone https://github.com/Paidguy/music-stack.git
cd music-stack
# 2. Run auto-installer
chmod +x scripts/setup.sh
sudo ./scripts/setup.sh
# 3. Set VNC password
vncserver
# Enter password, choose 'n' for view-only
# 4. Edit domain in Caddyfile
nano Caddyfile
# Replace 'paidguy.duckdns.org' with your domain
# 5. Start everything
sudo systemctl start novnc
docker compose up -d
# 6. Access your desktop
# Browser: https://your-domain.com:8443/vnc.html
# Music player: https://your-domain.comFirst time with Docker or Linux? Skip to Detailed Installation below.
Connect to your server via SSH and update the system:
# Update package lists and upgrade installed packages
sudo apt update && sudo apt upgrade -y
# Install essential tools
sudo apt install -y git curl wget nanoDocker runs the containerized services. Install it with these commands:
# Download Docker's official installation script
curl -fsSL https://get.docker.com -o get-docker.sh
# Run the installation script
sudo sh get-docker.sh
# Add your user to docker group (avoids needing sudo every time)
sudo usermod -aG docker $USER
# Install Docker Compose
sudo apt install -y docker-compose
# Log out and back in for group changes to take effect
exit
# Then reconnect via SSH
# Verify installation
docker --version
docker-compose --versionGet the project files onto your server:
# Navigate to your home directory
cd ~
# Clone the repository
git clone https://github.com/Paidguy/music-stack.git
# Enter the project directory
cd music-stack
# Check the contents
ls -laYou should see files like Caddyfile, docker-compose.yml, and the scripts/ directory.
This script installs all the desktop environment components:
# Make the script executable
chmod +x scripts/setup.sh
# Run it (requires sudo for system-wide installation)
sudo ./scripts/setup.shWhat this script does:
- Installs XFCE desktop environment
- Installs TigerVNC server
- Installs noVNC and websockify
- Downloads SpotiFLAC AppImage
- Creates systemd service for noVNC
- Sets up proper file permissions
β±οΈ This takes 5-10 minutes depending on your internet connection.
Set up your VNC password (this is what you'll use to access the desktop):
# Start VNC for the first time
vncserverYou'll be prompted for:
- Password: Choose a secure password (6-8 characters minimum)
- View-only password: Type
n(not needed for this use case)
The VNC server will start, then we'll stop it to ensure proper configuration:
# Stop the VNC session
vncserver -kill :1Verify the startup script exists:
# Check if xstartup file exists and is executable
ls -la ~/.vnc/xstartup
# If it doesn't exist or isn't executable, create it:
cat > ~/.vnc/xstartup << 'EOF'
#!/bin/bash
xrdb $HOME/.Xresources
startxfce4 &
EOF
chmod +x ~/.vnc/xstartupEdit the Caddyfile to use your actual domain:
nano CaddyfileReplace paidguy.duckdns.org with your domain in both places:
Before:
paidguy.duckdns.org {
reverse_proxy navidrome:4533
}
paidguy.duckdns.org:8443 {
reverse_proxy host.docker.internal:6080
}
After (example with your domain):
your-domain.duckdns.org {
reverse_proxy navidrome:4533
}
your-domain.duckdns.org:8443 {
reverse_proxy host.docker.internal:6080
}
Save and exit: Press Ctrl+X, then Y, then Enter
The music and data folders store your library and application data:
# Create directories if they don't exist
mkdir -p ~/music-stack/music
mkdir -p ~/music-stack/data
# Set proper permissions
chmod 755 ~/music-stack/music
chmod 755 ~/music-stack/dataStart the noVNC service (makes desktop accessible via browser):
# Enable it to start on boot
sudo systemctl enable novnc
# Start it now
sudo systemctl start novnc
# Check status
sudo systemctl status novncYou should see "active (running)" in green.
Start the Docker containers:
# Make sure you're in the project directory
cd ~/music-stack
# Start Navidrome and Caddy
docker compose up -d
# Check that they're running
docker compose psBoth containers should show "Up" status.
View logs if needed:
# Watch all logs
docker compose logs -f
# Or specific service
docker compose logs -f navidrome
docker compose logs -f caddyPress Ctrl+C to exit logs.
Launch the VNC server with your preferred resolution:
# Standard 1080p desktop
vncserver -geometry 1920x1080 -depth 24
# Or for lower bandwidth/smaller screens
vncserver -geometry 1280x720 -depth 24
# Or for high-DPI displays
vncserver -geometry 2560x1440 -depth 24Verify it's running:
ps aux | grep vncYou should see a Xtightvnc process running.
Test your virtual desktop:
Open a browser and go to:
https://your-domain.com:8443/vnc.html
Enter your VNC password. You should see the XFCE desktop.
Test your music player:
Go to:
https://your-domain.com
Create your admin account on first visit.
This project now supports configuration via environment variables for easier deployment. Create a .env file from the example:
cp .env.example .envEdit .env to set your domain:
# Your domain name (e.g., yourdomain.duckdns.org)
DOMAIN=your-domain.duckdns.org
# User/Group IDs for Syncthing (defaults to 1000)
UID=1000
GID=1000The Caddyfile will automatically use the DOMAIN environment variable, so you don't need to manually edit it.
The docker-compose.yml file defines your services. Here are the key sections:
services:
navidrome:
image: deluan/navidrome:latest
ports:
- "4533:4533"
environment:
ND_SCANSCHEDULE: "@every 15m" # How often to scan for new music
ND_LOGLEVEL: "info" # Logging verbosity
ND_SESSIONTIMEOUT: "24h" # How long you stay logged in
ND_ENABLETRANSCODINGCONFIG: "true" # Allow format conversion
volumes:
- "./music:/music" # Your music library
- "./data:/data" # Database and cache
user: "1000:1000" # Run as your user (prevents permission issues)Change scan frequency:
ND_SCANSCHEDULE: "@every 1h" # Scan hourly
ND_SCANSCHEDULE: "@every 30m" # Scan every 30 minutesAdjust session timeout:
ND_SESSIONTIMEOUT: "168h" # Stay logged in for 1 weekChange logging level:
ND_LOGLEVEL: "debug" # More detailed logs
ND_LOGLEVEL: "error" # Only show errorsAfter making changes:
docker compose down
docker compose up -dCaddy handles routing and SSL. The configuration is simple:
# Route traffic to music player
your-domain.com {
reverse_proxy navidrome:4533
}
# Route traffic to virtual desktop
your-domain.com:8443 {
reverse_proxy host.docker.internal:6080
}
Key point: host.docker.internal allows containers to access services running on the host machine.
If you're running on a system with limited RAM (like 1GB):
Create swap space:
# Create 4GB swap file
sudo fallocate -l 4G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
# Make it permanent
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstabAdd memory limits to docker-compose.yml:
services:
navidrome:
deploy:
resources:
limits:
memory: 512M
reservations:
memory: 256MThe virtual desktop is your music management workspace. Here's how to use it:
-
Open your browser and navigate to:
https://your-domain.com:8443/vnc.html -
Click "Connect" on the noVNC landing page
-
Enter your VNC password (the one you set during installation)
-
You're in! You should see the XFCE desktop
Launch the application:
- Right-click on the desktop
- Select "Open Terminal"
- Run these commands:
cd ~/music-stack
./SpotiFLAC.AppImage --no-sandboxBefore downloading anything:
-
In SpotiFLAC, go to Settings
-
Set Download Location to:
/home/YOUR_USERNAME/music-stack/musicReplace
YOUR_USERNAMEwith your actual username (usuallyubuntuor your chosen name) -
Why this matters: Only files in this exact location will appear in Navidrome!
Download your music:
- Search for albums or playlists
- Select quality settings (FLAC recommended)
- Click download
- Files appear immediately in Navidrome (scans every 15 minutes by default)
If you have music on your computer, you can upload it:
From Linux/Mac:
scp -r /path/to/your/music/* username@your-server:~/music-stack/music/From Windows:
Best practices for folder structure:
music/
βββ Artist Name/
β βββ Album Name (Year)/
β β βββ 01 - Track Name.flac
β β βββ 02 - Track Name.flac
β β βββ cover.jpg
β βββ Another Album (Year)/
βββ Another Artist/
Navidrome automatically reads metadata from your FLAC files and organizes everything.
Access: Simply visit https://your-domain.com
First-time setup:
- Click "Create Admin User"
- Choose username and password
- Log in
Features:
- Browse by artist, album, genre
- Create playlists
- Search across your library
- Control playback quality
- View album artwork
- Scrobble to Last.fm (optional)
CMS uses the Subsonic API, which means it works with dozens of apps.
iOS Apps (Recommended):
| App | Price | Best For |
|---|---|---|
| Amperfy | Free | Open-source, feature-rich |
| Play:Sub | $4.99 | Premium experience, offline mode |
| iSub | $2.99 | Classic, reliable |
Android Apps (Recommended):
| App | Price | Best For |
|---|---|---|
| Symfonium | $4.99 | π Best overall, tons of features |
| DSub | Free | Feature-rich, stable |
| Ultrasonic | Free | Open-source |
| SubStreamer | $3.99 | Clean interface |
Configuration (same for all apps):
Server URL: https://your-domain.com
Port: (leave empty or enter 443)
Username: (your Navidrome username)
Password: (your Navidrome password)
Tips:
- Most apps will auto-detect the correct port
- Enable "Use HTTPS" if it's a separate option
- Some apps call it "Server Address" instead of URL
- Test the connection before enabling offline mode
Troubleshooting connection:
- Try without the port first
- If that fails, explicitly set port to
443 - Make sure you're using
https://nothttp:// - Verify credentials work in the web player first
Symptom: You connect to the desktop but only see a gray screen with an X cursor.
Cause: The XFCE startup script is missing or not executable.
Fix:
# Stop VNC
vncserver -kill :1
# Recreate the startup script
cat > ~/.vnc/xstartup << 'EOF'
#!/bin/bash
xrdb $HOME/.Xresources
startxfce4 &
EOF
# Make it executable
chmod +x ~/.vnc/xstartup
# Restart VNC
vncserver -geometry 1920x1080 -depth 24Symptom: Browser shows "This site can't be reached" at https://your-domain.com
Diagnose:
# Check if containers are running
docker compose ps
# Check Caddy logs for SSL errors
docker compose logs caddy
# Verify domain resolves correctly
nslookup your-domain.com
# Test locally on the server
curl -I http://localhost:4533Common fixes:
If Caddy isn't getting SSL:
- Verify ports 80 and 443 are open in your firewall
- Check that your domain actually points to your server's IP
- Wait a few minutes - Let's Encrypt can be slow
If Navidrome isn't running:
docker compose restart navidrome
docker compose logs -f navidromeSymptom: Double-clicking SpotiFLAC does nothing, or shows permission error.
Fix:
# Make it executable
chmod +x ~/music-stack/SpotiFLAC.AppImage
# Install FUSE library (required for AppImages)
sudo apt install -y libfuse2
# Launch from terminal with sandbox disabled
cd ~/music-stack
./SpotiFLAC.AppImage --no-sandboxSymptom: You downloaded music but it doesn't show up in the web player.
Check download location:
# List files in music directory
ls -la ~/music-stack/music
# Verify files are actually there
find ~/music-stack/music -name "*.flac"Fix permissions:
# Ensure Navidrome can read the files
sudo chown -R $USER:$USER ~/music-stack/music
chmod -R 755 ~/music-stack/musicForce a scan:
- Go to Navidrome web UI
- Click the Activity icon (top right)
- Select "Full Scan"
Or restart Navidrome:
docker compose restart navidromeCause: High resolution or slow network connection.
Solutions:
Reduce resolution:
vncserver -kill :1
vncserver -geometry 1280x720 -depth 16Reduce color depth:
vncserver -geometry 1920x1080 -depth 16Adjust noVNC compression (in the noVNC interface):
- Click the sidebar
- Lower "Quality" slider
- Lower "Compression Level" slider
Check status:
sudo systemctl status novncView logs:
sudo journalctl -u novnc -n 50Common fix - recreate the service:
sudo tee /etc/systemd/system/novnc.service > /dev/null << EOF
[Unit]
Description=noVNC Web Gateway
After=network.target
[Service]
Type=simple
User=$USER
ExecStart=/usr/share/novnc/utils/launch.sh --vnc localhost:5901 --listen 6080
Restart=on-failure
RestartSec=10
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable novnc
sudo systemctl start novncSymptom: Docker fails to start with "port already in use" error.
Find what's using the port:
sudo netstat -tulpn | grep :4533
sudo netstat -tulpn | grep :6080Kill the process or change ports in docker-compose.yml
Check resource usage:
docker statsReduce Navidrome's resource usage:
Edit docker-compose.yml:
services:
navidrome:
environment:
ND_SCANSCHEDULE: "@every 1h" # Scan less frequently
ND_IMAGECACHESIZE: "100MB" # Reduce cacheAdd memory limits:
deploy:
resources:
limits:
memory: 512Mmusic-stack/
β
βββ π Caddyfile # Reverse proxy configuration
β # Handles domain routing and SSL
β
βββ π docker-compose.yml # Docker services definition
β # Defines Navidrome and Caddy containers
β
βββ π .gitignore # Git exclusion rules
β # Prevents music/data from being committed
β
βββ π README.md # This file
β
βββ π music/ # π΅ YOUR MUSIC LIBRARY
β β # Downloaded files go here
β βββ Artist/ # Organized by artist
β β βββ Album/
β β βββ track01.flac
β β βββ cover.jpg
β βββ ...
β
βββ π data/ # Application persistent data
β β # Auto-generated by Navidrome
β βββ navidrome.db # Database (playlists, user data)
β βββ cache/ # Transcoded files, thumbnails
β
βββ π scripts/ # Installation scripts
βββ setup.sh # Main installer
βββ novnc.service # systemd service definition
| Path | Purpose | Mount |
|---|---|---|
~/music-stack/music |
Your music library | β /music in Navidrome |
~/music-stack/data |
Navidrome database | β /data in Navidrome |
~/.vnc/ |
VNC server config | Host only |
/usr/share/novnc/ |
noVNC application | Host only |
All services now include health checks. Check their status:
# View health status of all services
docker compose ps
# Check specific service health
docker inspect music-navidrome-1 --format='{{.State.Health.Status}}'
# View health check logs
docker inspect music-navidrome-1 --format='{{json .State.Health}}' | jqHealth check indicators:
- healthy - Service is running correctly
- starting - Service is starting up (wait up to 10s)
- unhealthy - Service failed health checks (investigate logs)
If a service becomes unhealthy:
# Check service logs
docker compose logs navidrome
# Restart the service
docker compose restart navidromeMake VNC start automatically:
crontab -e
# Add this line:
@reboot sleep 30 && /usr/bin/vncserver -geometry 1920x1080 -depth 24Ensure Docker services start:
# Docker should already auto-start, but verify:
sudo systemctl enable docker
# Make Docker Compose services restart automatically
cd ~/music-stack
docker compose up -d --restart alwaysBackup script (~/backup-cms.sh):
#!/bin/bash
BACKUP_DIR=~/backups/cms
DATE=$(date +%Y%m%d-%H%M%S)
mkdir -p $BACKUP_DIR
# Backup music library
tar -czf $BACKUP_DIR/music-$DATE.tar.gz ~/music-stack/music/
# Backup Navidrome database
tar -czf $BACKUP_DIR/data-$DATE.tar.gz ~/music-stack/data/
# Backup configuration
cp ~/music-stack/docker-compose.yml $BACKUP_DIR/docker-compose-$DATE.yml
cp ~/music-stack/Caddyfile $BACKUP_DIR/Caddyfile-$DATE
# Keep only last 7 days
find $BACKUP_DIR -name "*.tar.gz" -mtime +7 -delete
find $BACKUP_DIR -name "*.yml" -mtime +7 -delete
echo "Backup completed: $DATE"Make it executable and run weekly:
chmod +x ~/backup-cms.sh
crontab -e
# Add: 0 3 * * 0 ~/backup-cms.shUpdate Docker images:
cd ~/music-stack
docker compose pull
docker compose up -dUpdate system packages:
sudo apt update && sudo apt upgrade -yUpdate SpotiFLAC:
- Download latest release manually from GitHub
- Replace old AppImage with new one
Use SSH keys only:
# On your local machine:
ssh-keygen -t ed25519 -C "your_email@example.com"
ssh-copy-id username@your-server
# On server, disable password auth:
sudo nano /etc/ssh/sshd_config
# Set: PasswordAuthentication no
sudo systemctl restart sshdSet up UFW firewall:
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow ssh
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw allow 8443/tcp
sudo ufw enableStrong VNC password:
vncpasswd
# Enter a complex password (12+ characters)Create additional Navidrome users:
- Log in as admin
- Go to Settings β Users
- Click Create User
- Each user gets separate playlists and preferences
Note: All users share the same music library.
Benefits: DDoS protection, caching, analytics
- Add your domain to Cloudflare
- Update nameservers with your domain registrar
- Create A record pointing to your server IP
- Set SSL/TLS mode to "Full (strict)"
- No changes needed in Caddyfile - it handles Cloudflare automatically
Run these two commands to turn everything on:
Start the Desktop (VNC):
vncserver -geometry 1280x720Start the Website Bridge:
sudo systemctl start novncYou can now go to https://your-domain.com:8443/vnc.html and connect.
Run these to turn it off (saves RAM when you aren't downloading music):
Stop the Desktop:
vncserver -kill :1Stop the Website Bridge:
sudo systemctl stop novncIf the screen is gray or stuck, run this full cycle to reset it:
# 1. Kill the Desktop
vncserver -kill :1
# 2. Restart the Bridge
sudo systemctl restart novnc
# 3. Start the Desktop again
vncserver -geometry 1280x720| Action | Command |
|---|---|
| Start Desktop | vncserver -geometry 1280x720 |
| Kill Desktop | vncserver -kill :1 |
| Start Web Bridge | sudo systemctl start novnc |
| Stop Web Bridge | sudo systemctl stop novnc |
| Restart Web Bridge | sudo systemctl restart novnc |
| Check Status | sudo systemctl status novnc |
| Start Docker Services | cd ~/music-stack && docker compose up -d |
| Stop Docker Services | docker compose down |
| Restart Navidrome | docker compose restart navidrome |
| View Logs | docker compose logs -f |
| Check Container Status | docker compose ps |
- Music Player:
https://your-domain.com - Virtual Desktop:
https://your-domain.com:8443/vnc.html - Admin Panel:
https://your-domain.comβ Settings β Administration
- Music Library:
~/music-stack/music - App Data:
~/music-stack/data - Config Files:
~/music-stack/Caddyfile,~/music-stack/docker-compose.yml - VNC Config:
~/.vnc/xstartup
This project stands on the shoulders of excellent open-source software:
- Navidrome - Modern music server and streamer
- Caddy - Fast web server with automatic HTTPS
- noVNC - Browser-based VNC client
- TigerVNC - High-performance VNC server
- XFCE - Lightweight desktop environment
- Docker - Container platform
βοΈ Copyright Compliance: This project is designed for personal use with legally obtained music. You are responsible for ensuring you have the rights to download, store, and stream any content.
π΅ Support Artists: Please purchase music through legitimate channels. This tool is meant to enhance your music experience, not replace fair compensation for creators.
π License: This project is provided as-is for educational and personal use. The authors are not responsible for misuse.
- π Bug Reports: GitHub Issues
- π¬ Questions: GitHub Discussions
- π Navidrome Docs: navidrome.org/docs
Contributions are welcome! Please see our CONTRIBUTING.md for detailed guidelines.
Quick Start:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Make your changes and test them
- Run validations:
docker compose config --quietandshellcheck scripts/*.sh - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
All pull requests are automatically validated by our CI/CD pipeline which checks:
- Docker Compose configuration validity
- Shell script syntax with shellcheck
- YAML formatting
- Documentation completeness
- Configuration best practices (e.g., ND_SCANSCHEDULE format)
Your personal music empire awaits.
π΅ Access from anywhere β’ πΌ Stream in lossless quality β’ πΎ Own your library
β Star this repo if you found it helpful!
π Found an issue? Open a GitHub issue
π¬ Have questions? Start a discussion
Made with β€οΈ for music lovers who value ownership, quality, and privacy