Watches your torrent client for downloads and automatically hardlinks them to the right directories.
673
██████╗ ██╗ ██╗ ██╗ ██████╗██╗ ██╗
██╔══██╗██║ ██║ ██║██╔════╝██║ ██╔╝
██████╔╝██║ ██║ ██║██║ █████╔╝
██╔═══╝ ██║ ██║ ██║██║ ██╔═██╗
██║ ███████╗╚██████╔╝╚██████╗██║ ██╗
╚═╝ ╚══════╝ ╚═════╝ ╚═════╝╚═╝ ╚═╝
Lightweight, label-based torrent media sorter.
Pluck watches your torrent client. Matches labels to rules. Moves files. Nothing more.
If you self-host a media server (Jellyfin, Plex, Audiobookshelf, Calibre-web) alongside a torrent client, you need a way to route completed downloads to the right library folder. The *arr stack is powerful — but overkill if you just need "label X → folder Y".
That's what pluck does. Nothing more, nothing less.
Like Sonarr's import, but without the rest of Sonarr.
Map torrent labels to destination folders. One rule per line: label:destination.
# rules.conf
audiobook:/data/media/audiobooks
ebook:/data/media/ebooks
music:/data/media/music
movie:/data/media/movies
tv:/data/media/tv
Labels are matched case-insensitively.
Audiobook,AUDIOBOOK, andaudiobookall match the same rule.
services:
pluck:
image: roomelephant/pluck:latest
restart: unless-stopped
environment:
- PLUCK_CLIENT=transmission
- PLUCK_CLIENT_URL=http://transmission:9091
- PLUCK_MODE=hardlink
- PLUCK_WATCH_DIR=/data/downloads/complete
volumes:
- ./rules.conf:/config/rules.conf
# Mount the common parent as a single volume (required for hardlink mode)
- /data:/data
Available on Docker Hub (
roomelephant/pluck) and GHCR (ghcr.io/room-elephant/pluck).
Add labels in your torrent client. When a download completes, pluck picks it up and places it in the matching directory.
Torrents with multiple labels are processed per label — each label gets its own placement.
┌─────────────────┐ 3. Read Label ┌─────────┐ 5. Place File ┌──────────────────────┐
│ Torrent Client │◀───────────────────│ │───────────────────▶│ /media/audiobooks/ │
│ (labels) │ │ pluck │ │ /media/ebooks/ │
└─────────────────┘ 2. File added │ │ │ /media/music/ │
│ ┌─────────────────▶│ │ └──────────────────────┘
│ │ └─────────┘
│ 1. Done │ │
▼ │ │ 4. Check Rules
┌─────────────────┐ │ ▼
│ Watch Dir │─┘ rules.conf
│ /data/downloads│ label:directory
└─────────────────┘
Pluck detects completed torrents two ways:
| Trigger | How |
|---|---|
| Filesystem watcher | Uses fsnotify to watch the downloads directory recursively. Fires after a 2-second debounce to let files settle. |
| Periodic rescan | Catches torrents labeled after completion. Default: every 60 minutes, configurable via PLUCK_RESCAN_INTERVAL. |
All configuration is done via environment variables:
| Variable | Default | Description |
|---|---|---|
PLUCK_CLIENT | transmission | Torrent client type |
PLUCK_CLIENT_URL | http://transmission:9091 | Client URL |
PLUCK_MODE | hardlink | hardlink, symlink, or copy |
PLUCK_WATCH_DIR | /data/downloads | Directory to watch for new files |
PLUCK_RULES_FILE | /config/rules.conf | Path to rules file |
PLUCK_STATE_FILE | /config/state.txt | Path to state history file |
PLUCK_RESCAN_INTERVAL | 3600 | Seconds between periodic rescans |
PLUCK_DRY_RUN | false | Log actions without executing them |
PLUCK_LOG_LEVEL | info | debug, info, or error |
# One rule per line: label:destination
# Labels are matched case-insensitively
# Lines starting with # are comments
audiobook:/data/media/audiobooks
ebook:/data/media/ebooks
music:/data/media/music
movie:/data/media/movies
tv:/data/media/tv
| Mode | Behavior | Cross-filesystem? | Disk usage |
|---|---|---|---|
hardlink (default) | Hard-links files; source and destination share the same disk blocks | ❌ No | None |
symlink | Creates a symbolic link pointing to the source file | ✅ Yes | None |
copy | Copies files to the destination | ✅ Yes | Doubles usage |
Tip: Use
hardlinkwhen downloads and media share a filesystem. Usesymlinkacross different filesystems. Usecopywhen you need the most reliability, but it doubles disk usage.
💡 Note on Ownership & Visibility: Pluck natively reads and replicates the exact User ID and Group ID (
UID/GID) of your original downloads and applies them directly to the placed files and directories. This guarantees that your files retain perfect visibility and permissions for your media server.
⚠️ Hardlink mode: Docker treats each bind-mount as a separate filesystem. To allow hardlinking between your downloads and media folders, mount their common parent (e.g.,
/data:/data) as a single volume. Mounting them individually will cause across-device linkerror.
⚠️ Symlink mode: Ensure your media server container (Jellyfin, Plex, etc.) also mounts the downloads directory at the same path, so symlinks can resolve correctly.
| Client | Status |
|---|---|
| Transmission | ✅ Supported |
| qBittorrent | 🔜 Planned |
| Deluge | 🔜 Planned |
Test your rules without touching any files:
docker run --rm \
-e PLUCK_DRY_RUN=true \
-e PLUCK_LOG_LEVEL=debug \
-v ./rules.conf:/config/rules.conf \
-v /data/downloads:/data/downloads \
ghcr.io/Room-Elephant/pluck:latest
Docker:
docker build -t pluck .
Native (Go 1.26+):
go build -o pluck ./cmd/pluck
Content type
Image
Digest
sha256:b4b47e273…
Size
2.7 MB
Last updated
7 months ago
docker pull roomelephant/pluck