Skip to content

Folder Structure

Pro-Tweaker edited this page Aug 2, 2026 · 6 revisions

Folder Structure

Note

This tree is created by the filetree core role. It is the authoritative layout for the whole stack: every path below is what the roles actually configure. Source: roles/filetree/ in the SEEDbox repo.

Based on:

Table of Contents

Overview

data_folder (default /mnt/storage, set in settings.yml) is the root of the data tree. It is bind-mounted as a single volume on /data in every container.

That single mount is the whole point. Because the download tree and the media library live on the same filesystem inside the container, the PVRs can hardlink completed downloads into the library instead of copying them, and moves are atomic. Splitting /downloads and /media into two mounts is the classic mistake that turns every import into a full copy and doubles disk usage.

The tree

data
├── torrents
│  ├── incomplete
│  │  ├── qbittorrent
│  │  └── rutorrent
│  └── complete
│     ├── books
│     ├── comics
│     ├── documentaries
│     ├── games
│     ├── mangas
│     ├── misc
│     ├── movies
│     ├── music
│     ├── software
│     └── tv
├── usenet
│  ├── incomplete
│  │  └── sabnzbd
│  └── complete
│     ├── books
│     ├── comics
│     ├── documentaries
│     ├── games
│     ├── mangas
│     ├── misc
│     ├── movies
│     ├── music
│     ├── software
│     └── tv
└── media
   ├── books
   ├── comics
   ├── documentaries
   ├── games
   ├── mangas
   ├── misc
   ├── movies
   ├── music
   ├── software
   └── tv

The incomplete/<client> subfolders are not created by filetree. Each download client role creates its own on deploy.

Categories

The same ten categories are created under torrents/complete, usenet/complete and media:

Category Consumed by
books Readarr, Kavita
comics Kavita
documentaries manual
games manual
mangas Kavita
misc catch-all, default SABnzbd category
movies Radarr, Jellyfin
music Lidarr, Navidrome
software manual
tv Sonarr, Jellyfin

Important

These names are hardcoded in roles/filetree/tasks/main.yml and kept in sync with the categories configured in the download clients. If you add a category, add it in both places or downloads will land in a folder the PVRs do not watch.

Ownership and permissions

Every directory is created as {{ user }}:{{ group }} (defaults debian:docker) with symbolic mode u=rwX,g=rwX,o=rX, which resolves to 0775 on directories. The capital X sets the execute bit on directories only, so files created later are not made executable.

Containers run as the uid and gid from settings.yml (defaults 1000 and 998), which must match that owner and group for hardlinking to work across services.

How the data flows

  1. A PVR (Sonarr, Radarr, Lidarr, Readarr) sends a release to a download client with a category.
  2. The client downloads into <protocol>/incomplete/<client>/.
  3. On completion the client moves the files to <protocol>/complete/<category>/.
  4. The PVR imports, hardlinking into media/<category>/.
  5. The media servers (Jellyfin, Navidrome, Kavita) read from media/ only.

Because step 4 is a hardlink, the file exists in both complete and media while occupying disk space once. Deleting the torrent later does not remove the imported media.

Per-service paths

Service Container mount Incomplete Complete
qBittorrent /data /data/torrents/incomplete/qbittorrent /data/torrents/complete/<category>
ruTorrent /data (state) plus /downloads /downloads/incomplete/rutorrent /downloads/complete/<category>
SABnzbd /data /data/usenet/incomplete/sabnzbd /data/usenet/complete/<category>
NZBGet /data /data/usenet/nzbget/intermediate /data/usenet/<category>
Sonarr /data root folder /data/media/tv
Radarr /data root folder /data/media/movies
Lidarr /data root folder /data/media/music
Readarr /data root folder /data/media/books
Kavita /data /data/media/books, /comics, /mangas

The rTorrent exception

ruTorrent is the one service that cannot take the single /data mount. The crazymax/rtorrent-rutorrent image hardcodes /data for its own state and /downloads for the download tree (cfg.basedir and cfg.download in .rtlocal.rc, both const).

The role therefore mounts:

Host path Container path
{{ rutorrent.folder }} /data
{{ data_folder }}/torrents /downloads

Both still resolve to the same host filesystem, so hardlinks keep working. But the paths the client reports to the PVRs (/downloads/complete/tv) are not the paths the PVRs see (/data/torrents/complete/tv).

Important

If you use ruTorrent with the PVRs, you must add a remote path mapping in each PVR (Settings, Download Clients, Remote Path Mappings):

Host Remote Path Local Path
rutorrent /downloads/ /data/torrents/

Clone this wiki locally