Skip to content

docker and nas

Alexander Herling edited this page Oct 12, 2026 · 14 revisions

Docker and NAS

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).

Quick start

docker compose -f docker/compose.yml up -d

Then 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 rdownloader shows progress; the line to wait for is rDownloader listening.

Running the published image

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:latest

Verify 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.com

The 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.

Restarting from the interface

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.

Volumes

Path Holds Back up
/config SQLite database, installed plugins, encrypted credentials, vendor/ Yes
/downloads Fallback destination for a download with no category Your call

Mount every path you use as a storage root

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/series

Then 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.

PUID, PGID and file ownership

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   # -> PGID

Both 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.

Time zone

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"

External tools in the image

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.

Scanning packages with ClamAV

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/clamav

Then 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.

Monitoring with Prometheus and Grafana

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:ro

with 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:

Prometheus's target health page: the rdownloader scrape pool with its metrics endpoint, its instance and job labels, the last scrape a moment ago and the state UP

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.

Environment variables

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 and other NAS devices

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, /downloads and /media below /mnt/user/, with PUID 99 (Unraid's nobody) and PGID 100 (users).
  • TrueNAS SCALE (24.10 or newer) — the compose file packaging/truenas/compose.yml for Apps → Discover Apps → ⋮ → Install via YAML. Create the datasets it mounts first, replace tank with your pool's name, and give the apps user (568) write access to them — or set PUID/PGID to the owner.

The three things that decide whether it works:

  1. Every storage root has a volume.
  2. PUID/PGID match the folder owner on the host.
  3. TZ is set.

Remote access

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.

Troubleshooting

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

Related pages

Reference in the repository: docker/README.md

Clone this wiki locally