Recyclarr v8 on a systemd timer

A daily one-shot sync of TRaSH guide profiles and minimum sizes into Radarr and Sonarr.

  • One-shot job on a systemd timer
  • Rootless: systemctl --user
  • Tested with podman 5.0 and newer

Last updated

In short: Recyclarr runs as a one-shot quadlet (Type=oneshot, Exec=sync) that recyclarr.timer starts once a day, so there is no container idling in between. The :8 tag with Pull=newer gets every v8 release but never a breaking v9. The config adds TRaSH profiles next to your own, and sets TRaSH minimum sizes while it keeps your own max caps, because plain TRaSH raises every max to unlimited.

The quadlet files

Every quadlet file below passes podman's quadlet generator and systemd-analyze verify on podman 5.0 and the current release. Personal values are placeholders: replace example.com, /srv/... paths and the time zone with your own.

recyclarr.container

Save as ~/.config/containers/systemd/recyclarr.container

[Unit]
Description=Recyclarr sync of TRaSH guide profiles to Radarr and Sonarr (one-shot)
After=network-online.target radarr.service sonarr.service

[Container]
# No :latest upstream. The major tag gets every v8 release through auto-update, while a
# v9 with breaking config changes never arrives unattended.
Image=ghcr.io/recyclarr/recyclarr:8
ContainerName=recyclarr
AutoUpdate=registry
# podman auto-update only checks running containers, and a timer job is almost never
# running, so every run pulls a newer :8 itself.
Pull=newer
# With an argument the image runs the CLI once instead of its internal cron.
Exec=sync
Environment=TZ=Etc/UTC
Secret=recyclarr-radarr-api-key,type=env,target=RADARR_API_KEY
Secret=recyclarr-sonarr-api-key,type=env,target=SONARR_API_KEY
Volume=recyclarr.volume:/config
Volume=%h/.config/recyclarr/recyclarr.yml:/config/recyclarr.yml:ro,Z

[Service]
Type=oneshot
TimeoutStartSec=15min
# No [Install]: recyclarr.timer starts it (or you, by hand).

recyclarr.volume

Save as ~/.config/containers/systemd/recyclarr.volume

[Unit]
Description=Recyclarr data: guide repository cache, state, logs

[Volume]
VolumeName=recyclarr

recyclarr.timer

Save as ~/.config/systemd/user/recyclarr.timer

[Unit]
Description=Daily Recyclarr sync of TRaSH guide profiles

[Timer]
OnCalendar=*-*-* 05:15:00
RandomizedDelaySec=15min
Persistent=true

[Install]
WantedBy=timers.target

recyclarr.yml

Save as ~/.config/recyclarr/recyclarr.yml

# Recyclarr v8 config: ~/.config/recyclarr/recyclarr.yml, mounted read-only.
# API keys come from podman secrets as RADARR_API_KEY and SONARR_API_KEY.
#
# Additive: the TRaSH profiles are created next to your own profiles, nothing is deleted
# (delete_old_custom_formats: false) and scores are only reset inside managed profiles.
#
# quality_definition sets the TRaSH MINIMUM sizes (rejects fakes and tiny re-encodes).
# The max and preferred values below keep your own caps: without them, TRaSH raises every
# max to unlimited, which also changes what your own profiles download. Copy your current
# values from Settings, Quality in Radarr and Sonarr.

radarr:
  radarr:
    base_url: http://host.containers.internal:7878
    api_key: !env_var RADARR_API_KEY
    delete_old_custom_formats: false
    quality_definition:
      type: movie
      qualities:
        - name: WEBDL-1080p
          max: 100
          preferred: 95
        - name: WEBRip-1080p
          max: 100
          preferred: 95
    quality_profiles:
      - trash_id: d1d67249d3890e49bc12e275d989a7e9  # HD Bluray + WEB
        reset_unmatched_scores:
          enabled: true
    custom_format_groups:
      add:
        - trash_id: f8bf8eab4617f12dfdbd16303d8da245  # [Optional] Golden Rule HD
        - trash_id: a3ac6af01d78e4f21fcb75f601ac96df  # [Unwanted] Unwanted Formats

sonarr:
  sonarr:
    base_url: http://host.containers.internal:8989
    api_key: !env_var SONARR_API_KEY
    delete_old_custom_formats: false
    quality_definition:
      type: series
      qualities:
        - name: WEBDL-1080p
          max: 130
          preferred: 95
        - name: WEBRip-1080p
          max: 130
          preferred: 95
        - name: Bluray-1080p
          max: 155
          preferred: 95
    quality_profiles:
      - trash_id: 72dae194fc92bf828f32cde7744e51a1  # WEB-1080p
        reset_unmatched_scores:
          enabled: true
    custom_format_groups:
      add:
        - trash_id: 158188097a58d7687dee647e04af0da3  # [Optional] Golden Rule HD
        - trash_id: 85fae4a2294965b75710ef2989c850eb  # [Streaming Services] HD/UHD boost
        - trash_id: 59c3af66780d08332fdc64e68297098f  # [Unwanted] Unwanted Formats

Why is it built this way?

What it is. Recyclarr copies the quality profiles, custom formats and size limits from the TRaSH guides into Radarr and Sonarr, and keeps them current when the guides change. It pairs with the media stack pod.

A job, not a daemon. Type=oneshot plus Exec=sync makes the container run one sync and exit. The timer starts it daily at a random minute between 05:15 and 05:30; Persistent=true catches up after the server was off. Build other schedules with the cron to systemd timer converter.

TRaSH minimums, your own maximums. The minimum sizes from TRaSH reject fake and badly re-encoded releases, which is what most people want. The TRaSH maximums are unlimited, though, and they are global: they also apply to your own profiles. The overrides in recyclarr.yml keep your current caps, so a full disk stays a choice you make.

Secrets as environment. !env_var RADARR_API_KEY in the YAML reads the variable that Secret=...,type=env fills from the podman secret. The config file holds no keys and can live in git.

Check that it works

systemctl --user list-timers recyclarr.timer
systemctl --user start recyclarr.service
journalctl --user -u recyclarr.service -n 50 --no-pager
podman secret ls

Recyclarr has no web page, so there is nothing to put behind Caddy. The journalctl command builder helps you filter the sync logs. For timers in general, see systemctl and journalctl.

How do I start it?

Run these as your normal user, not as root. Quadlet reads ~/.config/containers/systemd/ for user services.

mkdir -p ~/.config/containers/systemd ~/.config/systemd/user ~/.config/recyclarr
# API keys from Radarr and Sonarr (Settings, General), typed in and never saved in a file
read -rs -p 'Radarr API key: ' k && printf '%s' "$k" | podman secret create recyclarr-radarr-api-key - && unset k
read -rs -p 'Sonarr API key: ' k && printf '%s' "$k" | podman secret create recyclarr-sonarr-api-key - && unset k
systemctl --user daemon-reload
# first run by hand, then read what it changed
systemctl --user start recyclarr.service
journalctl --user -u recyclarr.service -n 50
systemctl --user enable --now recyclarr.timer

Check the result with systemctl --user status and the logs with journalctl --user -u followed by the service name. To turn a docker-compose file of your own into files like these, use the compose to quadlet converter.

Common mistakes

After the first sync, Radarr downloads huge remux files

TRaSH's quality_definition sets every max size to unlimited. List the qualities you care about under quality_definition.qualities with your current max and preferred values, as in the config above, so only the minimums change.

recyclarr.timer is active but the image never updates

podman auto-update only looks at running containers, and a one-shot job is not running at the time of the update. Pull=newer in the quadlet makes every run check the registry itself.

The sync fails with 401 Unauthorized

The secret holds a wrong or old API key. Recreate it with podman secret create --replace, then run systemctl --user start recyclarr.service again; secrets are read when the container starts.

systemctl --user enable recyclarr.service fails

The service has no [Install] section on purpose: only the timer starts it. Enable recyclarr.timer instead.

Frequently asked questions

Will Recyclarr delete my own quality profiles?

Not with this config. It creates the TRaSH profiles as new profiles, leaves your own profiles alone, and delete_old_custom_formats: false keeps custom formats it did not create.

How do I preview a sync before it changes anything?

Run the container once with the preview flag: podman run --rm with the same secrets and mounts, and the command sync --preview. It prints the changes and writes nothing.

Why is the timer file not in the containers folder?

Quadlet only reads its own file types (.container, .volume, .pod and so on). A .timer is a plain systemd unit, so it goes in ~/.config/systemd/user/, and it starts recyclarr.service, the unit quadlet generates from recyclarr.container.