All apps · 0 apps
droppedneedle
OfficialDocker app from Harvey's Repository
Overview
Readme
View on GitHub
Request music. Own everything.
Self-hosted music requests and discovery with a built-in library and download engine that drives your own clients. No Lidarr. One container.
Search the full MusicBrainz catalogue, request the album or the single track you actually want, and DroppedNeedle takes it from there: it drives your own download client, scores the candidates, verifies every file, and shelves the result in your library. Play it back from Jellyfin, Navidrome, Plex, or local files, or let any Subsonic or Jellyfin app play from you.
[!NOTE] DroppedNeedle only drives a download client you supply and run yourself over its local HTTP API. It never joins a P2P network, ships no indexers, and hosts no audio. What you fetch, and what your client shares back, is your call and your responsibility.
Contents
- See it
- Quick start
- What it does
- Download sources
- Configuration
- Troubleshooting
- For builders
- Support and license
See it

More screenshots

Quick start
You need Docker, a music library, and a download client. The example below uses slskd; SABnzbd with Newznab indexers or Prowlarr works too. DroppedNeedle runs neither for you. See slskd and Usenet.
1. Save this compose file
Images live on Docker Hub as droppedneedle/droppedneedle:latest.
services:
droppedneedle:
image: droppedneedle/droppedneedle:latest
container_name: droppedneedle
environment:
- PUID=1000 # run `id` on your host to find your user/group ID
- PGID=1000
- UMASK=027 # secure default; 002 for trusted group-writable media
- PORT=8688
- TZ=Etc/UTC # e.g. Europe/London, America/New_York
- SLSKD_DOWNLOADS_PATH=/data/slskd/complete
ports:
- "8688:8688"
volumes:
- ./config:/app/config
- ./cache:/app/cache
- ./plugins:/app/plugins # omit and installed plugins vanish on recreate
# One shared parent mount keeps the library and completed downloads on one
# boundary so imports move fast. Do not nest extra binds under /data.
- /path/to/media:/data:rw
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8688/health"]
interval: 30s
timeout: 10s
start_period: 10m
[!TIP] The full annotated compose, including an optional slskd sidecar, is docker-compose.example.yml.
2. Start it
docker compose up -d
Then open http://localhost:8688.
3. First run
- Create the first admin account. This happens once.
- Add your library path under Settings > Library, using the in-container path (e.g.
/data/music). - Add your download client under Settings > Download Client, then Test and Save.
- Hit Scan on Settings > Library. The scan reads and identifies files without renaming or retagging anything.
- Search the catalogue, open an album, and Request it. Watch it land live on the Downloads page.
Updating and the dev tag
Update like any other container:
docker compose pull
docker compose up -d
The first boot after an upgrade can take a while on large libraries. DroppedNeedle upgrades a copy of the database, checks it, and only then swaps it in, keeping a backup under /app/cache/upgrade-backups. If validation fails it restores the backup instead of starting half upgraded. Check the container log before retrying a newer image.
A :dev tag (droppedneedle/droppedneedle:dev) is built from main on every push and may break. Pin a commit with :dev-<short-sha>.
What it does
| Thing | What you get |
|---|---|
| Request | Whole albums or single tracks from the MusicBrainz catalogue, with an approval queue for the User role |
| Wanted | Failed and partial requests get re-searched on their own until a verified copy shows up |
| Quality | Per-format quality floors, automatic upgrades when a better copy appears, storage caps and quotas |
| Player | Queue, shuffle, 10-band EQ, embedded lyrics, live now-playing updates |
| Discovery | Trending, charts, genre browsing, recommendations from your ListenBrainz and Last.fm history, and a weekly mix that can queue up to five missing albums |
| Live events | Ticketmaster and Skiddle gig alerts for artists you follow, each user with their own cities |
| Following | New-release radar with optional auto-download, release-type filters, and sidebar badges |
| Library | Browse, filter, rescan, and remove albums; unmatched files wait in a manual-review queue |
| Free Music | Internet Archive items under Creative Commons or public-domain licences, licence shown up front, no account or API key, off with one toggle |
| Drop imports | Drag in a zip or loose files from anywhere you buy music; identified, tagged, and shelved, or held for a manual match |
| Playlists | Mix Jellyfin, Navidrome, Plex, local, YouTube, and Spotify imports in one place, share read-only with one toggle |
| Library Management | Optional MusicBrainz Picard-style tags, artwork, and organization behind an admin preview with dry run and typed confirmation. Off until you enable it |
How a request moves:
flowchart LR
A[Request] --> B[Score candidates]
B -->|confident| C[Your client downloads]
B -->|shaky| P[Parked for review]
C --> D[Verify files]
D -->|clean| E[Library]
D -->|bad| Q[Quarantined]
Q -. excluded next time .-> B
The engine searches your client, ranks candidates, and auto-accepts a confident match. Close calls park for review instead of guessing. Every file is verified (readable tags, sane duration, optional fingerprint check) before it touches the library, and bad sources get quarantined so they stop winning.
| Direction | What plugs in |
|---|---|
| In | Your slskd, your SABnzbd with your Newznab indexers or Prowlarr, Internet Archive free licences, and drop imports. Identity from MusicBrainz and AcoustID, artwork from the Cover Art Archive and AudioDB |
| Out | Jellyfin, Navidrome, Plex, local files, and YouTube previews, plus OpenSubsonic and Jellyfin APIs so apps like Symfonium, Finamp, Feishin, Amperfy, Jellify, and Manet can play from you |
| Around | ListenBrainz and Last.fm scrobbling, Spotify playlist import, Ticketmaster and Skiddle gigs, Deezer and iTunes preview clips, and purchase links that put Bandcamp first |
Download sources
slskd
DroppedNeedle talks to your own running slskd over its local HTTP API (X-API-Key). You bring slskd; DroppedNeedle drives it.
- Use slskd 0.25.0 or newer (0.25.1 is the verified pin:
slskd/slskd:0.25.1), with a Soulseek account inside it. - Give DroppedNeedle the URL plus API key under Settings > Download Client, then Test and Save. The key is stored encrypted and never logged.
[!IMPORTANT] Soulseek bans clients that share nothing, so give slskd at least one shared folder or searches and downloads fail. Everything in a shared folder goes out to the network. Choose it like you mean it.
The downloads path is where most installs go wrong. Three rules:
- Expose slskd's completed-downloads directory to DroppedNeedle read-write under one shared parent mount (e.g. library at
/data/music, completions at/data/slskd/complete). - Point
SLSKD_DOWNLOADS_PATHat that exact directory, not its parent. - Skip nested binds under
/data. Each one is a new mount boundary, which drops imports to the slower copy fallback.
Keep slskd's incomplete directory on the same mount as its downloads directory: slskd can report a transfer Completed before a cross-mount move between the two finishes, so a split mount risks verifying half-copied files.
Minimal slskd.yml essentials
soulseek:
username: your-soulseek-username
password: your-soulseek-password
shares:
directories:
- /data/share # required: share something or the network bans you
directories:
downloads: /downloads # the sidecar path; bind its host dir into DroppedNeedle too
web:
authentication:
api_keys:
droppedneedle:
key: choose-a-long-random-key
Usenet
The second source is Usenet through SABnzbd. For searching, either add Newznab-compatible indexers one by one (NZBGeek, NZBPlanet, NZB.su, Slug, and others) or point at a Prowlarr that already has them. The engine searches your chosen side, enqueues NZBs in your SABnzbd, and imports finished files through the same scoring, verification, and quarantine pipeline as slskd.
- Expose SABnzbd's completed-downloads directory read-write, ideally under the same shared parent mount as the library so the mount rules hold. In SABnzbd, point its Downloads folder setting at the matching path (e.g.
/data/sabnzbd/complete). - Under Settings > Download Client, enable Usenet and enter your SABnzbd URL and API key.
- Under Settings > Indexers / Prowlarr, pick one search backend: add each indexer's URL plus API key, or enter your Prowlarr URL plus API key. Then Test and Save.
slskd and Usenet can run side by side; the source priority control picks who goes first.
Configuration
Everything user-editable lives in the web UI and lands in config/config.json. Environment is only for container basics:
| Variable | Default | What it is |
|---|---|---|
PUID |
1000 |
File owner inside the container (run id on the host) |
PGID |
1000 |
File group inside the container |
UMASK |
027 |
Creation mask for new files; 002 for trusted group-writable media |
PORT |
8688 |
Port the app listens on |
BIND_HOST |
auto |
auto listens on IPv4 and IPv6, dropping to IPv4 alone where IPv6 is off. Set 0.0.0.0, ::, or one interface IP to pin it |
TRUSTED_PROXY_IPS |
127.0.0.1,::1 |
IPs/CIDRs whose X-Forwarded-* headers are trusted; point it at your reverse proxy, listing every address family it arrives on |
TZ |
Etc/UTC |
Container timezone |
SLSKD_DOWNLOADS_PATH |
/data/downloads/slskd |
Exact in-container path to slskd completions (the compose example uses /data/slskd/complete) |
The app answers on IPv4 and IPv6, but Docker still has to publish the port on both. docker port droppedneedle should list 0.0.0.0:8688 and [::]:8688; if only the first appears, turn on IPv6 for the daemon ("ipv6" and "ip6tables" in /etc/docker/daemon.json). Pinning BIND_HOST to one interface IP answers only there: the upgrade readiness probe follows the pin automatically, but the container HEALTHCHECK still uses localhost, so keep a wildcard or loopback value unless you check that address yourself.
Permissions and NAS notes
Keep 027 for a private box. Use 002 when DroppedNeedle and another trusted service in the same group both write the same media. Skip 000: it makes new files writable by every local account the filesystem allows. UMASK shapes new files only; a move can keep the source mode the download client set.
Unraid commonly uses nobody:users (PUID 99, PGID 100). Point PUID and PGID at whoever owns the mounted config and cache paths. The container skips ownership changes it cannot make, which covers FUSE, NFS, CIFS, and rootless setups. A read-only config or cache mount refuses to start before anything gets written.
/app/config and /app/cache must be writable and honor SQLite locking, fsync, and atomic replacement. Plain bind mounts, named volumes, local Unraid shares, and TrueNAS datasets with normal permissions all qualify. NFS and SMB mounts only work when they support the same file locking SQLite needs. On Docker Desktop for Windows, prefer named volumes for those two paths.
| Data | Container path | Notes |
|---|---|---|
| Config and database | /app/config |
Persist it |
| Cover art and metadata cache | /app/cache |
Persist it |
| Plugins | /app/plugins |
Persist it or installs vanish on recreate |
| Drop-import staging | /app/imports |
Optional; without it, large uploads and unmatched files live on the container layer |
| Media | /data |
Shared parent for library (/data/music) and client completions |
Where things live in the UI:
| Setting | Location |
|---|---|
| Library paths, naming template, scan schedule, AcoustID key | Settings > Library |
| Library Management profiles, previews, recovery | Library Management (admin) |
| Download clients, indexers, quality tiers, verification, wanted watcher | Settings > Download Client |
| Subsonic and Jellyfin APIs, app passwords, transcoding | Settings > Connect Apps |
| Jellyfin | Settings > Jellyfin |
| Navidrome | Settings > Navidrome |
| Plex | Settings > Plex |
| Local files | Settings > Local Files |
| Last.fm app key (admin, once per instance) | Settings > Last.fm |
| YouTube API key | Settings > YouTube |
| Spotify client ID and secret | Settings > Spotify |
| Ticketmaster and Skiddle keys, sweep scope | Settings > Live Events |
| Scrobbling and discovery accounts | Profile > Scrobbling & Discovery |
| Home layout, release types, MusicBrainz source | Settings > Preferences |
| Users, roles, Jellyfin and Plex user import | Settings > Users |
| Password breach checking, HSTS | Settings > Security |
Link Last.fm from Profile > Scrobbling & Discovery after the admin saves the instance app key; link ListenBrainz with the token from your ListenBrainz profile. Artist images come from AudioDB (on by default, free key rate limits apply) with proxying and TTLs under Settings > Advanced.
Users and roles
| Role | Requests | Admin |
|---|---|---|
| Admin | Requests start immediately, no approval | Everything: users, approvals, all settings |
| Trusted | Requests start immediately, no approval | Nothing admin side |
| User | Requests wait for admin approval | Nothing admin side |
The first account is always admin. Later accounts are created by an admin or automatically on first Jellyfin, Plex, or OIDC sign-in (all start as User). Every login method toggles in the UI; no environment variables involved. Sessions last 30 days and die with the account if an admin deletes it.
Setting up OIDC
Any provider with the authorization code flow works (Authelia, Keycloak, Authentik, and others):
- Create a client in your provider with redirect URI
https://your-droppedneedle-url/api/v1/auth/oidc/callback. - Enter the issuer URL, client ID, and client secret under Settings > Security.
- Save. An SSO button appears on the login page.
Troubleshooting
- Downloads finish in the client but never import:
SLSKD_DOWNLOADS_PATHmust point at the exact completions directory, visible read-write. The Download Client page shows the path status and the reason. - Separate-mount warning: imports still work through copy-and-remove, which briefly needs room for both copies. One shared
/dataparent with no nested binds restores fast moves. - Client connection fails or returns 401: wrong URL or API key. Re-enter both under Settings > Download Client and Test.
- Searches return nothing or the network drops you: slskd needs shared folders and a healthy Soulseek connection. Leechers get banned.
- Scan finds nothing or files pile into manual review: check the library path is readable. Untagged files with no fingerprint match need a human.
More fixes
- Tier-3 fingerprinting stays off until you add an AcoustID API key. Without it, scans use tags and text matching only.
- MusicBrainz lookups pace at 1 request per second on the official MusicBrainz server. Built-in BrainzMash runs its own local pacing instead. Either way, later scans are incremental, so the first one is the slow one.
For builders
Interactive API docs (Swagger UI) live at /api/v1/docs on your instance. Every /api/v1/* route takes a Bearer token or the session cookie, everything under /api/v1/settings/* also needs Admin, and /health stays public for the container check.
Plugins are stable (api_version = 1, v0 still loads): Python running in-process with your server's full privileges and no sandbox. Install from a GitHub URL or a copied folder, read the code before you enable it. Nothing is bundled. Contract: PLUGINS.md. Walkthrough: docs/PLUGIN-CREATION.md.
Bug reports and feature requests go to Issues, code via PRs. Dev setup, tests, and style rules are in CONTRIBUTING.md.
Support and license
Docs: droppedneedle.com. Chat: Discord. Bugs and ideas: GitHub Issues.
DroppedNeedle is AGPL-3.0. Copyright (c) 2025 DroppedNeedle and contributors. For commercial licensing, write to contact@droppedneedle.com.
Media gallery
1 / 4Requirements
Categories
Download Statistics
Total Downloads Over Time
Related apps
Explore more like this
Explore allDetails
droppedneedle/droppedneedle:latestRuntime arguments
- Web UI
http://[IP]:[PORT:8688]- Network
bridge- Privileged
- false
Template configuration
Port the DroppedNeedle web interface listens on.
- Target
- 8688
- Default
- 8688
- Value
- 8688
Persistent application configuration and database.
- Target
- /app/config
- Default
- /mnt/user/appdata/droppedneedle/config
- Value
- /mnt/user/appdata/droppedneedle/config
Cover art and metadata cache.
- Target
- /app/cache
- Default
- /mnt/user/appdata/droppedneedle/cache
- Value
- /mnt/user/appdata/droppedneedle/cache
Your music library root. Imported downloads land here, so it is read-write. Keep it on the same filesystem as the downloads mounts below for fast atomic-rename imports.
- Target
- /music
- Default
- /mnt/user/Music
- Value
- /mnt/user/Music
slskd's COMPLETED-downloads directory (its Options > Directories > downloads path, often a .../complete folder). Must be the exact folder, not a parent. Keep it on the same filesystem as /music. Leave empty if you do not use Soulseek.
- Target
- /slskd-downloads
Container path where the slskd Downloads mount above appears. Must match that mount's container path (/slskd-downloads); imports fail if it does not. Leave as-is unless you change the mount.
- Target
- SLSKD_DOWNLOADS_PATH
- Default
- /slskd-downloads
- Value
- /slskd-downloads
Optional (Usenet): SABnzbd's completed-downloads directory (its complete_dir). Must match the Downloads mount set in Settings > Download Client > SABnzbd. Keep it on the same filesystem as /music. Leave empty if you do not use Usenet.
- Target
- /sabnzbd-downloads
User ID for file permissions inside the container. Run 'id' on your Unraid terminal to find your value.
- Default
- 99
- Value
- 99
Group ID for file permissions inside the container. Run 'id' on your Unraid terminal to find your value.
- Default
- 100
- Value
- 100
Your timezone. Examples: America/New_York, Europe/Berlin, Asia/Tokyo.
- Target
- TZ
- Default
- Europe/London
- Value
- Europe/London
Octal file-creation mask. 027 keeps files private to the owner and readable by the group; use 002 only when trusted group members need write access.
- Default
- 027
- Value
- 027