docker and nas - degoya/rDownloader GitHub Wiki
The container image carries the service, the embedded web interface and the external helper binaries. It does not carry the desktop capture agent — on a NAS, use the browser extension or run the agent on your desktop against the remote service.
Images for linux/amd64 and linux/arm64 are published to the GitHub Container Registry
(ghcr.io) for every release, tagged with the version (v1.2.3); latest follows the newest
release. Each holds the same rdownloader binary as that release's Linux archive, on
debian:trixie-slim, signed with cosign and with an SBOM and build provenance.
The image is the way to run rDownloader on an older Linux. The native Linux archives need
glibc 2.39 or newer; Debian 12 and Raspberry Pi OS bookworm have 2.36, and the binaries do not
start there (GLIBC_2.39 not found). The image carries its own glibc and runs there — on a
Raspberry Pi with a 64-bit OS as well (linux/arm64).
docker compose -f docker/compose.yml up -dThen open http://127.0.0.1:8710 and work through the setup wizard.
The first start takes about a minute while the bundled plugins are installed and loaded; the health check allows 90 seconds.
docker logs -f rdownloadershows progress; the line to wait for isrDownloader listening.
docker run -d --name rdownloader --restart unless-stopped \
-p 127.0.0.1:8710:8710 \
-v rdownloader-config:/config \
-v /srv/media/movies:/media/movies \
-e PUID=1000 -e PGID=1000 -e TZ=Europe/Berlin \
ghcr.io/degoya/rdownloader:latestVerify the signature before you trust it:
cosign verify ghcr.io/degoya/rdownloader:latest \
--certificate-identity-regexp 'https://github.com/degoya/rDownloader/.*' \
--certificate-oidc-issuer https://token.actions.githubusercontent.comThe compose example publishes on the host loopback only, runs as an unprivileged user with
no-new-privileges, and keeps /config and /downloads as persistent mounts.
A plugin installed or updated runs from the next start, and rDownloader offers Restart now
(under Settings → System → Updates, or by itself with Restart automatically when needed —
see Restarting when needed). In a container it does not start
itself again: it stops the clean way and ends with exit code 75, and the container's restart
policy starts it again — restart: unless-stopped in compose.yml, --restart unless-stopped
above. A container started without a restart policy stays stopped after such a restart; start it
with docker start rdownloader. Exit code 75 is only ever this restart: a stop you ask for
(docker stop) ends with 0 and stays stopped.
| Path | Holds | Back up |
|---|---|---|
/config |
SQLite database, installed plugins, encrypted credentials, vendor/
|
Yes |
/downloads |
Fallback destination for a download with no category | Your call |
This is the one mistake that costs data.
/downloads is only the fallback for a download with no category.
Storage roots are free absolute paths, and you will
usually have several: /media/movies, /media/series, /media/music.
A storage root at a path that is not mounted lives inside the container's writable layer. Downloads run normally, and all of it is deleted when the container is removed or recreated — which includes every image update.
Such a root gets a NOT PERSISTENT badge in Settings → Storage & rules → Storage roots, an explanation above the form, and a note on the readiness card in Settings → System → Status. It is a warning, not a refusal; if you see the badge, fix the mount before you download anything you want to keep.
So: for every storage root you intend to configure, add a volume first.
volumes:
- ./config:/config
- /srv/media/movies:/media/movies
- /srv/media/series:/media/seriesThen create the storage roots inside the application, pointing at /media/movies and
/media/series.
A Plex, Jellyfin or Emby container that should pick up new downloads mounts the same host folders as a library folder; rDownloader then asks it for a library scan when a package finishes, with a path mapping for the package's folder alone — see Plex, Jellyfin and Emby.
A bind mount belongs to a user on the host. Unless the user inside the container has the same numeric id, every write into it fails — this is the single most common reason a NAS setup does not work.
Find your ids on the host:
id -u # -> PUID
id -g # -> PGIDBoth have to be numeric ids greater than 0; anything else stops the container before an id is
changed, with the reason in docker logs. PUID=0 or PGID=0 would run the service as root, so
it starts only together with RDOWNLOADER_ALLOW_ROOT=1, when that is what you mean.
The container adopts those ids before the service runs. /downloads is not re-owned
recursively (on a NAS share that would take minutes on every start), so its ownership is yours to
set.
TZ defaults to UTC. Schedules, quiet hours, bandwidth windows and queue-completion actions
all run on the container's clock, so set it or everything fires at the wrong time:
environment:
TZ: "Europe/Berlin"The image ships ffmpeg, yt-dlp, streamlink, gallery-dl, 7-Zip, par2 and Apprise at pinned versions, visible under Settings → Tools → Status. Media downloads, streams, galleries, unpacking, PAR2 repair and Apprise notifications therefore work out of the box.
A newer binary can be dropped into /config/vendor, which is searched before PATH.
| Tool | Licence |
|---|---|
ffmpeg, ffprobe (Debian) |
GPL-2.0-or-later |
yt-dlp |
Unlicense |
streamlink |
BSD-2-Clause |
gallery-dl |
GPL-2.0-only |
apprise |
BSD-2-Clause |
7z (Debian 7zip) |
LGPL-2.1-or-later, parts BSD-3-Clause |
RAR codec for 7z (Debian non-free 7zip-rar) |
unRAR licence |
par2 |
GPL-2.0-or-later |
The full licence texts are in the image: /usr/share/doc/<package>/copyright for the Debian
packages, the *.dist-info folders under /opt/venv/lib/ for the pip ones.
RAR and RAR5 archives unpack; RAR is extract only. The image adds Debian's non-free 7zip-rar
codec to 7z; its unRAR licence allows extracting RAR archives and forbids creating them.
unrar itself is not included: 7z covers RAR5, and Debian's unrar-free cannot read it.
See External tools.
The malware scan needs a clamd, which the rDownloader
image does not include. Run the official ClamAV image beside it:
clamav:
image: clamav/clamav:1.5.4
restart: unless-stopped
volumes:
- ./clamav:/var/lib/clamavThen set the clamd address to clamav:3310 under Settings → Post-processing → Malware scan
(ClamAV) and press Test clamd. The first start takes a minute or two before clamd answers;
the volume keeps the signature database across restarts, and the image keeps it current by itself. clamd refuses files
larger than its StreamMaxLength (25 MiB by default); to scan larger ones, mount a clamd.conf
with a larger value and raise Largest scanned file to match.
The service publishes its queue, transfer and storage figures at /api/v1/metrics for
Prometheus. Create a token with only the Metrics area ticked (see
Statistics), put it in a file, and run Prometheus
beside the container:
prometheus:
image: prom/prometheus:v3.15.0
restart: unless-stopped
volumes:
- ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro
- ./prometheus/rdownloader.token:/etc/prometheus/rdownloader.token:rowith this prometheus/prometheus.yml:
scrape_configs:
- job_name: rdownloader
metrics_path: /api/v1/metrics
authorization:
credentials_file: /etc/prometheus/rdownloader.token
static_configs:
- targets: ['rdownloader:8710']Prometheus runs as the user nobody (65534) in its image, so the token file has to be readable
by it: sudo chown 65534 prometheus/rdownloader.token && chmod 600 prometheus/rdownloader.token.
Add a volume on /prometheus if the collected history should survive a new container. Under
Status → Target health the job shows up once the first scrape went through:

For Grafana, add this Prometheus as a data source (http://prometheus:9090 from a container in the
same project) and import the ready dashboard described under
Statistics.
The image sets these defaults:
| Variable | Default |
|---|---|
RDOWNLOADER_DATABASE |
/config/rdownloader.sqlite3 |
RDOWNLOADER_DOWNLOADS |
/downloads |
RDOWNLOADER_PLUGIN_ROOT |
/config/plugins |
RDOWNLOADER_BUNDLED_PLUGINS |
/usr/local/lib/rdownloader/plugins |
RDOWNLOADER_LISTEN |
0.0.0.0:8710 |
Plus PUID, PGID, TZ, RUST_LOG and RDOWNLOADER_ALLOW_ROOT.
Synology NAS walks through it step by step — preparing the folders, finding the user ids, creating the project in Container Manager, the first start, reaching it from outside and updating. The same shape applies to QNAP, Unraid and TrueNAS.
Two NAS systems have a ready file in the repository, filled in with the NAS's own user ids and paths:
-
Unraid — the template
packaging/unraid/rdownloader.xml. Add it under Docker → Add Container → Template repositories, or copy it to/boot/config/plugins/dockerMan/templates-user/on the flash drive. It maps/config,/downloadsand/mediabelow/mnt/user/, withPUID99 (Unraid's nobody) andPGID100 (users). -
TrueNAS SCALE (24.10 or newer) — the compose file
packaging/truenas/compose.ymlfor Apps → Discover Apps → ⋮ → Install via YAML. Create the datasets it mounts first, replacetankwith your pool's name, and give theappsuser (568) write access to them — or setPUID/PGIDto the owner.
The three things that decide whether it works:
- Every storage root has a volume.
-
PUID/PGIDmatch the folder owner on the host. -
TZis set.
Do not publish the container port directly. Put a TLS reverse proxy in front and tell rDownloader about it — the trusted range in Docker is the container network, not loopback. See Reverse proxy.
Host names. Behind a port mapping the host name is whatever you type into the browser. An IP
address, localhost, the host of the external URL and names that exist only on the local network
always work — http://nas:8710, http://syno-xyz.local:8710, or http://rdownloader:8710 from
another container. A name under a real or router domain — http://nas.lan:8710,
http://downloads.example.org — is refused until you add it under Settings → Security →
Reachability & reverse proxy → Allowed host names. See Reverse proxy.
A switched-off login does not apply in a container. It trusts only loopback, and behind Docker's port mapping every request arrives from the bridge network, so the password stays required.
| Symptom | Where to look |
|---|---|
| Container never becomes healthy |
docker logs rdownloader; wait for rDownloader listening
|
| The container stayed stopped after Restart now | It has no restart policy; docker start rdownloader, and add --restart unless-stopped
|
| Is the service alive? | curl -fsS http://127.0.0.1:8710/api/v1/health |
| Environment and paths sane? | docker exec rdownloader rdownloader doctor |
| No hoster resolvers | The startup log says no bundled plugin packages found
|
| Downloads fail with permission errors |
PUID/PGID do not match the folder owner on the host |
| A storage root is badged NOT PERSISTENT | The path is not mounted — add a volume for it |
| Media downloads fail |
Settings → Tools → Status; try a newer yt-dlp in /config/vendor
|
| Schedules fire at the wrong time |
TZ is unset, so the container runs on UTC |
- Installation
- Synology NAS
- Storage, categories and rules
- Reverse proxy
- External tools
- Troubleshooting
Reference in the repository:
docker/README.md