heddohon

heddohon

Docker app from zorcerer's Repository

Overview

A music player for a Navidrome, Subsonic or Jellyfin library, rendered on the server. Audio and artwork are fetched by Heddohon rather than by the browser, so the music server never has to be reachable from the devices you play on: one hostname is exposed and the library stays where it is. Sign-in is a live call to the music server, so there is no second set of accounts to keep. Streams the original file by default, up to FLAC 24/192, with no transcoding or resampling, and shows what is being decoded. Transcoding to MP3, Opus or AAC is a setting of each account. Synced lyrics, crossfade, a 10-band equaliser, ReplayGain, internet radio from the stations Navidrome lists, and a listening history with statistics, kept on this server. Links to songs, albums and playlists play without an account, and a link to listen together plays what the host plays. The queue and settings follow the account between devices, one browser can control another, and accounts that choose to can see what each other is playing. There is a phone layout, an installable app, and desktop apps for Linux and Windows.

Heddohon, a self-hosted music player for Navidrome, Subsonic and Jellyfin

Release Build Docker pulls License

Heddohon is a web music player you host yourself, in front of the Navidrome, Subsonic or Jellyfin server you already run. Screenshots and a tour are on heddohon.app.

What it does differently

Your music server stays private. The browser only talks to Heddohon, which fetches every byte from the music server and keeps the credentials on the server. The music server needs no public address.

Listen together, like a Spotify Jam. Send a link, and everyone who opens it hears what you play, as you play it, in their own browser and without an account. Friends with an account on your music server can add tracks to the shared queue.

Share a song, an album or a playlist with anyone. The link plays the original file without an account, until it expires or you withdraw it.

Your listening, kept on your server. Every play is recorded: top artists and albums, the hours you listen, what you played on this day in earlier years, and daily mixes made from it.

One player across your devices. The queue and settings follow the account, and one browser can control another.

Play anything from the keyboard. In Terminal, Cmd+K or Ctrl+K opens a command line: play and the name of an album, an artist or a song starts the best match. Queueing, mixes, seeking and the volume work the same way.

Four themes, coloured by the artwork. Terminal, Liquid, Paper and Phosphor.

Also

  • Original files up to FLAC 24/192, or transcoded to MP3, Opus or AAC.
  • Crossfade, ReplayGain, a sleep timer and an equaliser with AutoEq headphone corrections.
  • Synced lyrics, instant mixes, internet radio, and casting to Chromecast and AirPlay.
  • Scrobbling to Last.fm and ListenBrainz.
  • Desktop apps for Linux and Windows, and an experimental Android app (Android 14 and later).
  • AI assistants that speak MCP, such as Claude Code.

Quick start

curl -LO https://github.com/zorcerer/heddohon/releases/latest/download/docker-compose.yml
docker compose up -d
docker compose logs heddohon

The log shows a setup code. Open http://<server>:3000, enter the code and the address of your music server, and sign in with your account on it.

SETUP.md has the rest in order: moving the secret out of the data directory, and putting Heddohon on the internet behind a reverse proxy.

Configuration by environment variables, Unraid, running without Docker, and the image tags are in Configuration. The Android app is on each release, and Obtainium keeps it updated.

Install on Unraid from Community Applications

Documentation

Setup Starting it, the setup page, and putting it on the internet
Configuration Environment variables, reverse proxies, PostgreSQL, Unraid
Apps The desktop and Android apps
Security Threat model, known gaps, reporting a vulnerability
Contributing Reporting bugs, suggesting features, and sending pull requests

More in the wiki: audio, architecture and design notes.

AI disclosure

Written with agentic assistance from Claude. The code and security posture have been reviewed by me and by multiple AI-assisted audits, documented in SECURITY.md.

License

MIT. Music in the header image: Josh Woodward (CC BY 4.0) and J.S. Bach's The Art of the Fugue played by Kimiko Ishizaka (CC0).

Support Heddohon on Ko-fi

Media gallery

1 / 7

Requirements


    Nothing has to be filled in before the first start. With Secret and both music server URLs left empty, Heddohon prints a setup code to its log (Logs, on the container's icon) and serves a setup page at its address. The page takes the code and the address of your music server, makes the secret, and asks where Heddohon is opened, so that sign-in works there at once, over plain http on the LAN included.

    Or fill them in here, and the setup page is not shown:

    1. Secret. It encrypts the music-server credentials Heddohon stores. Generate one with: openssl rand -base64 48. Changing it later signs everyone out.

    2. At least one music server URL. It is resolved by the container, so a LAN address is the right answer: http://192.168.1.10:4533

    3. Public URL. The address you open Heddohon at, exactly as the browser shows it: http://192.168.1.10:3000 on the LAN, or https://music.example.com behind a reverse proxy. Sign-in is refused from any other address.

    4. Over plain http, set Secure cookie (under Advanced) to false, or the sign-in does not stick.

    The appdata folder is given to the user the container runs as when it starts: PUID and PGID under Advanced, 99 and 100 as Unraid has them. A container made from this template before 0.7.0 has --user 99:100 under Extra Parameters; with that left in, the folder has to be that user's already: chown -R 99:100 /mnt/user/appdata/heddohon

    The database is SQLite in appdata. The PostgreSQL settings under Advanced move it to a PostgreSQL server, and can be left empty.

    Every setting is described at https://github.com/zorcerer/heddohon/wiki/Configuration

Related apps

Explore more like this

Explore all

Details

Repository
ghcr.io/zorcerer/heddohon:latest
Last Updated2026-10-16
First Seen2026-09-17

Runtime arguments

Web UI
http://[IP]:[PORT:3000]
Network
bridge
Shell
bash
Privileged
false

Template configuration

WebUIPorttcp

Port the interface is served on.

Target
3000
Default
3000
Value
3000
AppdataPathrw

Holds the cover cache and the log files, and the SQLite database unless a PostgreSQL URL is set.

Target
/data
Default
/mnt/user/appdata/heddohon
Value
/mnt/user/appdata/heddohon
SecretVariable

Encrypts stored music-server credentials and derives session keys. Minimum 32 characters: openssl rand -base64 48. Changing it signs everyone out. Left empty, the first-run setup makes one and saves it in appdata.

Target
HEDDOHON_SECRET
Navidrome / Subsonic URLVariable

Base URL of your Navidrome or Subsonic server, resolved by this container and not by the browser, such as http://192.168.1.10:4533. Leave empty if you only use Jellyfin.

Target
HEDDOHON_SUBSONIC_URL
Jellyfin URLVariable

Base URL of your Jellyfin server, such as http://192.168.1.10:8096. Leave empty if you only use Navidrome.

Target
HEDDOHON_JELLYFIN_URL
Public URLVariable

The address you open Heddohon at, exactly as the browser shows it, with its scheme and port: http://192.168.1.10:3000 on the LAN, or https://music.example.com behind a reverse proxy. Sign-in and every other change are refused from an address that does not match. Left empty, the address is asked for by the first-run setup and can be changed by an administrator under Settings, Storage.

Target
ORIGIN
Secure cookieVariable

auto marks the session cookie Secure, which in this image is always, whatever the scheme. Set false if you reach this over plain http on the LAN, or sign-in will not stick.

Target
HEDDOHON_COOKIE_SECURE
Default
auto
Value
auto
Session hoursVariable

How long a sign-in lasts, in hours. 720 is 30 days. Not extended by activity.

Target
HEDDOHON_SESSION_HOURS
Default
720
Value
720
Application nameVariable

Shown on the sign-in screen and in the sidebar.

Target
HEDDOHON_APP_NAME
Default
Heddohon
Value
Heddohon
Navidrome labelVariable

Name shown for this server on the sign-in screen.

Target
HEDDOHON_SUBSONIC_LABEL
Default
Navidrome
Value
Navidrome
Jellyfin labelVariable

Name shown for this server on the sign-in screen.

Target
HEDDOHON_JELLYFIN_LABEL
Default
Jellyfin
Value
Jellyfin
Sign-in hintVariable

An optional line of text on the sign-in screen, for telling users which account to use.

Target
HEDDOHON_LOGIN_HINT
Cover cacheVariable

Megabytes of cover art kept under the appdata path. The least recently used files are dropped once it is passed. 0 switches the cache off.

Target
HEDDOHON_COVER_CACHE_MB
Default
512
Value
512
Upstream timeoutVariable

Milliseconds to wait on the music server before giving up. A stream has no limit once it has started.

Target
HEDDOHON_UPSTREAM_TIMEOUT_MS
Default
20000
Value
20000
Shared linksVariable

true lets accounts share a song, an album or a playlist by link, playable by anyone with the link for 1, 7 or 30 days, and start a session to listen together (which also needs Remote control). false hides the share buttons and stops existing links from opening; they open again if this is set back to true.

Target
HEDDOHON_SHARING
Default
true
Value
true
DownloadsVariable

The Download original link in the player's track details. false removes it and its route.

Target
HEDDOHON_DOWNLOADS
Default
true
Value
true
Remote controlVariable

The player's Devices button: control playback on another browser signed in to the same account and, with the Navidrome plugin, pick a track up from another app. false removes it and its routes, and with it Listening now and listening together.

Target
HEDDOHON_REMOTE_CONTROL
Default
true
Value
true
Listening nowVariable

Lets accounts on this server see what each other is playing. An account is shown only after it turns this on under Settings, Account, where it also sets a display name and a picture. false removes it and its routes. Needs Remote control.

Target
HEDDOHON_LISTENERS
Default
true
Value
true
Default themeVariable

The theme an account starts in, and the one the sign-in page is drawn in: terminal, liquid, paper or phosphor. Each account can change its own under Settings.

Target
HEDDOHON_DEFAULT_THEME
Default
terminal
Value
terminal
User IDVariable

The user the server runs as, and the owner the appdata folder is given when the container starts. 99 is nobody on Unraid.

Target
PUID
Default
99
Value
99
Group IDVariable

The group the server runs as, and the group the appdata folder is given when the container starts. 100 is users on Unraid.

Target
PGID
Default
100
Value
100
First-run setupVariable

When the Secret or every music server address is left empty, Heddohon prints a code to its log as it starts and serves a setup page that takes the code. false switches that off: a start without them is then an error.

Target
HEDDOHON_SETUP
Default
true
Value
true
Folder viewVariable

The Folders page, which browses the library as it is laid out on disk. false removes the page, its links and playing a folder.

Target
HEDDOHON_FOLDERS
Default
true
Value
true
Internet radioVariable

The Radio page, for the internet radio stations Navidrome lists. A station is played through this server, which fetches its stream. false removes it.

Target
HEDDOHON_RADIO
Default
true
Value
true
Radio stations on private addressesVariable

true allows a station whose stream is on a private or loopback address, for a stream served on your own network. Otherwise such a station is refused.

Target
HEDDOHON_RADIO_PRIVATE
Default
false
Value
false
Navidrome plugin tokenVariable

Optional. Set it to let the Heddohon plugin for Navidrome (heddohon.ndp, from github.com/zorcerer/heddohon-navidrome-plugin) tell this server what is played in other apps: those plays go into the listening history, an import brings in the whole scrobble history, and the Devices button lists the other apps. At least 32 characters and not the Secret: openssl rand -hex 32. The plugin's settings in Navidrome take the same value. Empty leaves it off.

Target
HEDDOHON_NAVIDROME_PLUGIN_TOKEN
AI assistants (MCP)Variable

true lets each account make a token under Settings, AI, for an AI assistant that speaks the Model Context Protocol, such as Claude Code. The assistant then searches that account's library at /api/mcp and, if the token allows it, makes and changes playlists, favourites and ratings. What it reads goes to whoever runs its model. Left empty, it is off until the first-run setup or an administrator under Settings, AI, turns it on; true or false here decides it, and Settings does not offer the switch.

Target
HEDDOHON_MCP
Lyrics from LRCLIBVariable

true fetches lyrics from LRCLIB for tracks the music server has none for, sending the artist, title, album and length of each such track to LRCLIB when its lyrics are opened.

Target
HEDDOHON_LYRICS_LRCLIB
Default
false
Value
false
LRCLIB URLVariable

The LRCLIB to ask. Filled in with lrclib.net; change it for a self-hosted one. Only read when Lyrics from LRCLIB is true.

Target
HEDDOHON_LYRICS_LRCLIB_URL
Default
https://lrclib.net
Value
https://lrclib.net
Headphone corrections from AutoEqVariable

true lets listeners search the AutoEq database for their headphones under Settings, Playback, Equaliser. The server fetches the index once a day and each correction chosen from GitHub, which learns this server's address and the headphones chosen.

Target
HEDDOHON_AUTOEQ
Default
false
Value
false
AutoEq URLVariable

AutoEq's results directory to read. Filled in with the one on GitHub; change it for a mirror. Only read when Headphone corrections from AutoEq is true.

Target
HEDDOHON_AUTOEQ_URL
Default
https://raw.githubusercontent.com/jaakkopasanen/AutoEq/master/results
Value
https://raw.githubusercontent.com/jaakkopasanen/AutoEq/master/results
Plays to a Discord channelVariable

true lets an account paste a Discord webhook address under Settings, Account, and posts each track it plays to that channel. The server then sends the title, artist, album and cover of what that account plays to Discord.

Target
HEDDOHON_DISCORD
Default
false
Value
false
ListenBrainz from this serverVariable

true lets each account paste a ListenBrainz user token of its own under Settings, Account, and scrobbles what it plays. The server then sends the title, artist and album of what that account plays to ListenBrainz. An account that linked ListenBrainz through Navidrome keeps that one instead.

Target
HEDDOHON_LISTENBRAINZ
Default
false
Value
false
ListenBrainz URLVariable

The ListenBrainz API to send to. Filled in with listenbrainz.org; change it for a server of your own. Only read when ListenBrainz from this server is true.

Target
HEDDOHON_LISTENBRAINZ_URL
Default
https://api.listenbrainz.org
Value
https://api.listenbrainz.org
Log levelVariable

error, warn, info or debug. The default writes nothing while the server is working. debug times every call to the music server, for working out why a page is slow.

Target
HEDDOHON_LOG_LEVEL
Default
error
Value
error
Log formatVariable

text writes key=value lines. json writes one JSON object a line, for a log collector.

Target
HEDDOHON_LOG_FORMAT
Default
text
Value
text
Slow request, in millisecondsVariable

A request that takes this long or longer is logged as request-slow, at the warn level. 0 switches that off.

Target
HEDDOHON_LOG_SLOW_MS
Default
2000
Value
2000
Log files kept, in daysVariable

The log is also written to the logs folder in the data directory, one file a day. This is how many days of files are kept; older ones are deleted. 0 writes no files.

Target
HEDDOHON_LOG_KEEP_DAYS
Default
7
Value
7
Android app fingerprintsVariable

Optional. The server already vouches for the released Android app, so it opens without an address bar. For an app you built and signed yourself, put its certificate's SHA-256 fingerprint here (several separated by commas).

Target
HEDDOHON_ANDROID_FINGERPRINTS
PostgreSQL URL (optional)Variable

Optional. Leave empty to keep the database as SQLite in appdata. Set to postgres://host:5432/database to keep it on a PostgreSQL server instead; an existing SQLite database is copied across on the first start.

Target
HEDDOHON_DATABASE_URL
PostgreSQL user (optional)Variable

Optional, only with a PostgreSQL URL. The user, if it is not in the URL.

Target
HEDDOHON_DATABASE_USER
PostgreSQL password (optional)Variable

Optional, only with a PostgreSQL URL. The password, if it is not in the URL.

Target
HEDDOHON_DATABASE_PASSWORD
PostgreSQL password file (optional)Variable

Optional, only with a PostgreSQL URL. A file inside the container holding the password, in place of the variable above.

Target
HEDDOHON_DATABASE_PASSWORD_FILE
PostgreSQL TLS (optional)Variable

Optional, only with a PostgreSQL URL. off, require (encrypted, certificate not checked) or verify-full. Empty uses the URL's sslmode.

Target
HEDDOHON_DATABASE_SSL
PostgreSQL import (optional)Variable

Optional, only with a PostgreSQL URL. true copies an existing SQLite database into an empty PostgreSQL one on the first start.

Target
HEDDOHON_DATABASE_IMPORT
Default
true
Value
true