rrr-stack: The Docker Compose File Behind My Arr Media Server
A service-by-service breakdown of rrr-stack, my one-command Docker Compose setup for Jellyseerr, Prowlarr, Sonarr, Radarr, Lidarr, qBittorrent, Jellyfin, Homepage, and Maintainerr.
A walkthrough of rrr-stack, the Docker Compose file that spins up my entire Arr-based media server — Jellyseerr, Prowlarr, Sonarr, Radarr, Lidarr, qBittorrent, Jellyfin, Maintainerr, and a Homepage dashboard — with a single command.
What rrr-stack is #
I put together my home media server as one docker-compose.yml, so the entire stack — request front-end, indexer manager, per-media-type managers, download client, media server, cleanup automation, and a dashboard — comes up (or goes down) with a single command instead of nine separate manual setups.
sudo docker-compose up -d # deploy everything
sudo docker-compose stop # stop everything
sudo docker-compose rm # remove containersThe compose file, service by service #
Homepage — the dashboard #
homepage:
image: ghcr.io/gethomepage/homepage:latest
ports:
- '3000:3000'
volumes:
- ${ARRPATH}homepage/config:/app/config
- ${ARRPATH}homepage/icons:/app/public/icons
- ${ARRPATH}homepage/data:/data
- /var/run/docker.sock:/var/run/docker.sock:roHomepage is the landing page for the whole stack — one dashboard with links and live widgets for every service below. The read-only Docker socket mount is what lets it pull live container status directly instead of just linking out blindly.
Jellyseerr — the request front door #
jellyseerr:
image: fallenbagel/jellyseerr
ports:
- '5055:5055'
volumes:
- ${ARRPATH}jellyseerr/config:/app/configThis is the only screen I actually interact with day to day. Search for a movie or show, hit request, and Jellyseerr hands it off to Sonarr or Radarr behind the scenes.
Maintainerr — automatic library cleanup #
maintainerr:
image: ghcr.io/jorenn92/maintainerr:latest
user: 1000:1000
environment:
- TZ=Europe/Brussels
volumes:
- ${ARRPATH}maintainerr/data:/opt/data
ports:
- '6246:6246'Maintainerr watches the library and removes old or unwatched titles based on rules I set — the piece that stops the media drive from just filling up forever.
Prowlarr — the shared indexer manager #
prowlarr:
image: linuxserver/prowlarr:latest
volumes:
- ${ARRPATH}prowlarr/config:/config
- ${ARRPATH}prowlarr/backup:/data/Backup
- ${ARRPATH}downloads:/downloads
ports:
- 9696:9696Prowlarr configures indexers once and syncs them out to Sonarr, Radarr, and Lidarr, instead of setting up the same indexers three separate times.
Sonarr, Radarr, Lidarr — one manager per media type #
sonarr:
image: linuxserver/sonarr:latest
volumes:
- ${ARRPATH}sonarr/config:/config
- ${ARRPATH}sonarr/backup:/data/Backup
- ${ARRPATH}sonarr/tvshows:/data/tvshows
- ${ARRPATH}downloads:/downloads
ports:
- 8989:8989
radarr:
image: linuxserver/radarr:latest
volumes:
- ${ARRPATH}radarr/config:/config
- ${ARRPATH}radarr/movies:/data/movies
- ${ARRPATH}radarr/backup:/data/Backup
- ${ARRPATH}downloads:/downloads
ports:
- 7878:7878
lidarr:
image: linuxserver/lidarr:latest
volumes:
- ${ARRPATH}lidarr/config:/config
- ${ARRPATH}lidarr/music:/data/musicfolder
- ${ARRPATH}downloads:/downloads
ports:
- 8686:8686Same shape, three times: TV, movies, and music each get their own manager, their own media folder, and a shared /downloads mount so they can all see what qBittorrent just pulled down.
qBittorrent — the download client #
qbittorrent:
image: linuxserver/qbittorrent:latest
labels:
- "com.centurylinklabs.watchtower.enable=false"
volumes:
- ${ARRPATH}qbittorrent/config:/config
- ${ARRPATH}downloads:/downloads
ports:
- 8080:8080
- 6881:6881
- 6881:6881/udp
environment:
- WEBUI_PORT=8080
- TORRENTING_PORT=6881
healthcheck:
start_period: 15sThe Watchtower label opts qBittorrent out of auto-updates specifically — a download client mid-transfer is exactly the kind of thing you don't want silently restarted underneath you. It also gets its own healthcheck grace period so it isn't flagged unhealthy while it's still starting up.
Jellyfin — the media server #
jellyfin:
image: linuxserver/jellyfin
ports:
- '8096:8096/tcp'
- '7359:7359/udp'
- '1900:1900/udp'
volumes:
- ${ARRPATH}jellyfin/config:/config
- ${ARRPATH}radarr/movies:/data/Movies
- ${ARRPATH}sonarr/tvshows:/data/TVShows
- ${ARRPATH}lidarr/music:/data/Music
- ${ARRPATH}readarr/books:/data/BooksJellyfin mounts the exact same folders Radarr, Sonarr, and Lidarr write into — so as soon as one of them imports a file, Jellyfin can see it without any copying step in between.
The conventions that hold it together #
A few small decisions do a lot of work across this file:
${ARRPATH}— every single volume path is prefixed with one environment variable, so moving the entire stack to a different disk or mount point is a one-line change instead of editing nine services.- A shared
/downloadsmount — Prowlarr, Sonarr, Radarr, Lidarr, and qBittorrent all see the same downloads folder, so a file qBittorrent finishes fetching is immediately visible to whichever*Arrapp requested it. env_file: .env— API keys, ports, and PUID/PGID live in one.envfile rather than being duplicated across services.restart: unless-stoppedeverywhere — the stack comes back up on its own after a reboot, without needing to remember to restart anything by hand.
Getting it running the first time #
Docker Compose brings the containers up, but each app still needs a first-run pass to actually talk to each other:
chowntheARRPATHfolder to the PUID/PGID set in.env, so every container can actually write to its config and media folders.- qBittorrent starts with a temporary password — pull it from
docker logs qbittorrent, log in, and set a permanent username/password (I also enabled "bypass authentication for clients on localhost"). - Prowlarr gets qBittorrent added as a download client — using the host machine's IP rather than
localhost, since these are separate containers. - Sonarr / Radarr / Lidarr each need a root folder set (
/data/tvshows,/data/movies,/data/musicfolder), qBittorrent added as a download client the same way, and their API key copied over into Prowlarr's "Apps" settings. - Back in Prowlarr, add indexers and hit "Sync App Indexers" — each connected app should show a green "full sync" once it's wired up correctly.
- Jellyfin gets a media library pointed at
/data/Movies,/data/TVShows,/data/Music, and/data/Books.
Once that's done, adding a movie in Radarr or a series in Sonarr and hitting "search" is enough to kick off the whole download → import → stream pipeline on its own.
The repo's folder structure and setup notes also reference readarr (books) and homarr (an earlier dashboard, since replaced by Homepage) — but they aren't part of the docker-compose.yml service list above. If book support or that second dashboard still matter, it's worth double-checking those are actually wired into the current compose file rather than left over from an earlier version.
Conclusion #
rrr-stack is deliberately boring in the best way: every service follows the same shape, every path runs through one env variable, and the whole thing comes up with docker-compose up -d. If you want to see the full file as-is, it's on GitHub at sabinsthnp/rrr-stack.
Links #
- Repository: github.com/sabinsthnp/rrr-stack