A self-hosted, multi-user Deemix that runs behind a reverse proxy for authentication and keeps a global download history. Forked from bambanah/deemix (originally by the very talented RemixDev).
What this fork changes:
- Proxy-header authentication — identity is delegated entirely to a reverse proxy (Authentik / Authelia / Traefik Forward Auth / Caddy). There is no in-app login. Every request must carry a trusted user header or it gets a 401.
- One shared Deezer account — admins configure a single ARL; every user searches and downloads through it.
- Global download history (SQLite) — every track download is recorded with the requesting user, and an "already downloaded / missing" indicator is shown across search and album/playlist views.
- Two tiers — admins (a configurable group) manage the ARL/config and see stats; standard users search, download, view history, and delete their own history entries.
- Docker-only, SQLite-only deployment. The Electron
guipackage has been removed.
- deezer-sdk — wrapper for Deezer's API
- deemix — the download engine (unchanged by this fork)
- webui — Vue.js + Express web interface (all fork changes live here)
The app trusts identity headers injected by an upstream proxy on every
request and attaches req.user = { username, groups, name, isAdmin }. If the
configured user header is missing, the request is rejected with 401 — the app
is never meant to be reachable without the proxy in front of it.
Header names and the admin group are configurable:
| Env var | Purpose | Default |
|---|---|---|
AUTH_USER_HEADER |
Header with the authenticated username | Remote-User |
AUTH_GROUP_HEADER |
Header with comma/pipe-separated groups | Remote-Groups |
AUTH_NAME_HEADER |
Header with the display name (optional) | Remote-Name |
ADMIN_GROUP |
Group whose members are admins | admins |
A user is an admin when ADMIN_GROUP appears in their groups header.
Local / no-proxy use: set
DEEMIX_SINGLE_USER=trueto bypass header auth and run as a single local admin (also howpnpm devand the test suite run). In production keepDEEMIX_SINGLE_USER=falseso the proxy is enforced.
- Admin — manage the shared Deezer ARL and all app config, view system stats (total downloads, per-user counts, download-dir disk usage), delete any history entry.
- Standard user — search the catalog, download albums, view the global library, delete only their own entries.
Downloads are scoped by type:
- Albums — any authenticated user.
- Individual tracks — everyone by default; set
TRACK_DOWNLOAD_GROUPto restrict. - Playlists — everyone by default; set
PLAYLIST_DOWNLOAD_GROUPto restrict. - Whole-artist / discography — everyone by default; set
DISCOGRAPHY_DOWNLOAD_GROUPto restrict.
With the group env vars unset (the default), all authenticated users can download anything. Set the group vars to restrict a download type to that group plus admins. The UI hides controls the user isn't permitted to use, and the server rejects them as a backstop.
Every completed download writes one SQLite row per track (Deezer id, title,
artist, album, type, file path, timestamp, requested_by, success/failed) to
DEEMIX_DB_PATH (default /config/history.db).
- The Library page (all users) presents those tracks grouped into albums (and playlists / singles) — a cover-art grid with an aggregate status (Downloaded / Partial / Missing), track count and requester. Click an album to see its tracks. Management is album-level: you can remove your own albums from the library, admins can remove any. Removing an album only deletes the history rows — the files on disk are kept.
- "Already downloaded" indicator — on search results and album/track views a badge shows Downloaded (a success row exists and the recorded file is still on disk) or Missing (success row exists but the file is gone). The Deezer track id is the cross-reference key, not the filename or tags.
The history DB uses SQLite's rollback journal (not WAL) on purpose:
/configis typically a bind mount, and WAL's shared-memory coordination is unreliable over Docker Desktop bind mounts (a fresh connection can read zero rows). The rollback journal persists reliably on every filesystem.
# from the repo root
docker compose up -d --buildThe provided docker-compose.yml defines a single
deemix service (no Redis, no Postgres — SQLite only), mounts ./downloads and
./config, and is not published to the host: route your reverse proxy to
deemix:6595 on a shared Docker network.
| Variable | Description | Default |
|---|---|---|
DEEMIX_SINGLE_USER |
Bypass proxy auth as a local admin | false |
ADMIN_GROUP |
Group granting admin | admins |
DEEMIX_DB_PATH |
SQLite history DB path | /config/history.db |
AUTH_USER_HEADER |
Username header | Remote-User |
AUTH_GROUP_HEADER |
Groups header | Remote-Groups |
AUTH_NAME_HEADER |
Display-name header | Remote-Name |
TRACK_DOWNLOAD_GROUP |
Group allowed to download tracks | (everyone) |
PLAYLIST_DOWNLOAD_GROUP |
Group allowed to download playlists | (everyone) |
DISCOGRAPHY_DOWNLOAD_GROUP |
Group allowed to download discographies | (everyone) |
FAVORITES_GROUP |
Group allowed to view the Favorites page | (everyone) |
CHARTS_GROUP |
Group allowed to view the Charts page | (everyone) |
DEEMIX_MUSIC_DIR |
Download directory | /downloads |
DEEMIX_DATA_DIR |
Config directory | /config |
DEEMIX_SERVER_PORT |
Listen port | 6595 |
PUID / PGID |
UID/GID for downloaded files | 1000 / 1000 |
deemix only needs the proxy to (a) authenticate the user and (b) forward the identity headers upstream. The defaults match Authelia out of the box; other providers just need the header names pointed at theirs.
Authelia returns Remote-User, Remote-Groups, Remote-Name — the deemix
defaults. Configure the Traefik middleware to copy them upstream:
http:
middlewares:
authelia:
forwardAuth:
address: "http://authelia:9091/api/authz/forward-auth"
authResponseHeaders:
- "Remote-User"
- "Remote-Groups"
- "Remote-Name"
- "Remote-Email"
routers:
deemix:
rule: "Host(`deemix.example.com`)"
service: deemix
middlewares: ["authelia"]No deemix env changes needed. Put users in a admins group (or set
ADMIN_GROUP) to grant admin.
Authentik forwards X-authentik-username, X-authentik-groups (pipe-separated),
and X-authentik-name. Point deemix at them:
environment:
AUTH_USER_HEADER: "X-authentik-username"
AUTH_GROUP_HEADER: "X-authentik-groups"
AUTH_NAME_HEADER: "X-authentik-name"
ADMIN_GROUP: "admins"Create an admins group in Authentik and add your admins to it.
Any forward-auth provider works as long as it injects a username header (and,
for admin support, a groups header). For nginx auth_request, copy the headers
with auth_request_set + proxy_set_header; for Caddy's forward_auth, use
copy_headers Remote-User Remote-Groups Remote-Name. Set AUTH_USER_HEADER /
AUTH_GROUP_HEADER / AUTH_NAME_HEADER to whatever your provider emits.
This repo uses pnpm and Turborepo.
corepack enable # enable pnpm
pnpm install # install deps (also regenerates the lockfile)
DEEMIX_SINGLE_USER=true pnpm dev # dev server on :6595, auth bypassed as adminUseful checks:
pnpm --filter deemix-webui type-check
pnpm --filter deemix-webui test
pnpm --filter deemix-webui buildNative dependency note: this fork adds
better-sqlite3for the history DB. It ships prebuilt binaries (fine on Windows/macOS/glibc Linux); on Alpine (musl) the Docker image compiles it using thepython3/make/g++already installed in the builder stage.
docker build -t deemix .