This article introduces how to build a personal home media center using Docker Compose.
The basic workflow is as follows:
- The core consists of Sonarr and Radarr. Sonarr finds TV shows, Radarr finds movies.
- Where do they find content? From indexers (think of them as content sources). With multiple indexers, you need a management tool, which is where Prowlarr comes in. It can connect to Sonarr and Radarr, so you only need to add indexers in Prowlarr, and they automatically sync to Sonarr and Radarr.
- Once content sources are found, you need a downloader. By default, qBittorrent is used, but Aria2 and Transmission are also available.
- Finally, Bazarr manages subtitles.
After setting up the server, you can use a client to watch movies.
Deployment
-
Create the following folder structure:
/media βββ config β βββ bazarr β βββ prowlarr β βββ qbittorrent β βββ radarr β βββ sonarr βββ docker-compose.yml βββ downloads βββ library βββ movies βββ tv -
Create a Docker network named
media:docker network create media -
Edit
docker-compose.yml:services: sonarr: image: lscr.io/linuxserver/sonarr:latest container_name: sonarr environment: - PUID=1000 - PGID=1000 - TZ=America/Toronto volumes: - ./config/sonarr:/config - ./downloads:/downloads - ./library/tv:/tv ports: - "8989:8989" networks: - media restart: unless-stopped radarr: image: lscr.io/linuxserver/radarr:latest container_name: radarr environment: - PUID=1000 - PGID=1000 - TZ=America/Toronto volumes: - ./config/radarr:/config - ./downloads:/downloads - ./library/movies:/movies ports: - "7878:7878" networks: - media restart: unless-stopped prowlarr: image: lscr.io/linuxserver/prowlarr:latest container_name: prowlarr environment: - PUID=1000 - PGID=1000 - TZ=America/Toronto volumes: - ./config/prowlarr:/config ports: - "9696:9696" networks: - media restart: unless-stopped bazarr: image: lscr.io/linuxserver/bazarr:latest container_name: bazarr environment: - PUID=1000 - PGID=1000 - TZ=America/Toronto volumes: - ./config/bazarr:/config - ./downloads:/downloads - ./library/movies:/movies - ./library/tv:/tv ports: - "6767:6767" networks: - media restart: unless-stopped flaresolverr: image: ghcr.io/flaresolverr/flaresolverr:latest container_name: flaresolverr environment: - PUID=1000 - PGID=1000 - LOG_LEVEL=info - TZ=America/Toronto networks: - media restart: unless-stopped qbittorrent: image: lscr.io/linuxserver/qbittorrent:latest container_name: qbittorrent environment: - PUID=1000 - PGID=1000 - TZ=America/Toronto - WEBUI_PORT=8080 volumes: - ./config/qbittorrent:/config - ./downloads:/downloads ports: - "9090:8080" - "6881:6881" - "6881:6881/udp" networks: - media restart: unless-stopped networks: media: external: true
Configuration
qBittorrent
-
Open qBittorrent. If you see
Unauthorized:-
Since my service is deployed on a Debian mini PC with only a command line, accessing qBittorrent from Windows is restricted. It may have external access disabled by default. You need to modify the configuration file.
-
First, stop the container:
docker compose down -
Open
/config/qbittorrent/qBittorrent/qBittorrent.conf:[Preferences] WebUI\Address=0.0.0.0 WebUI\CSRFProtection=false WebUI\HostHeaderValidation=false WebUI\ServerDomains=* WebUI\Username=adminChange
WebUI\Addressto0.0.0.0 -
Restart the container:
docker compose up -d -
View logs to get an initial password:
docker logs qbittorrent --tail 50 -
Enter admin and the initial password in your browser to login
-
After logging in, go to β β WebUI β Authentication to change the username and password
-
Prowlarr
-
Left sidebar, first option is Indexers. Add an Indexer. You can try a few free ones like EZTV and YTS.
-
When adding 1337x, you’ll see “blocked by CloudFlare Protection”. This is where FlareSolverr comes in:
- Settings β Indexers, add an Index using FlareSolverr. Since it’s on the same Docker network, the Host can be
http://flaresolverr:8191/ - Set tag to
1337x - Go back and add the Indexer, also set the tag to
1337x. Now testing the connection should succeed.
- Settings β Indexers, add an Index using FlareSolverr. Since it’s on the same Docker network, the Host can be
-
Log in to Sonarr and Radarr separately, set usernames and passwords (you’ll get API Keys), then in Prowlarr’s Settings β Apps, add Sonarr and Radarr:
Name: Sonarr Sync Level: Full Sync Prowlarr Server: http://prowlarr:9696 Sonarr Server: http://sonarr:8989 API Key: xxxName: Radarr Sync Level: Full Sync Prowlarr Server: http://prowlarr:9696 Radarr Server: http://radarr:7878 API Key: xxx -
In Settings β Download Clients, add the qBittorrent downloader:
Name: qBittorrent Enable: true Host: qbittorrent Port: 8080 Username: xxx Password: xxx
Sonarr / Radarr
- Similar to Prowlarr, enter Settings β Download Clients separately and add the qBittorrent downloader
Bazarr
- Bazarr doesn’t seem to require authentication. In Settings, you can bind Sonarr and Radarr. Like before, use container names for addresses.
- In Settings β Languages β Language Filter, add Simplified Chinese:
- Enable Embedded Tracks Language
- Create a Simplified Chinese profile
- Set all default profiles to Simplified Chinese
- In Settings β Providers, add subtitle sources like
zimukuandOpenSubtitles.com(the latter requires registration)
Finding Movies
Once everything is configured, the process is simple:
- To find movies, search in Radarr directly. Similarly, search for TV shows in Sonarr.
- Add to wishlist
- It automatically connects to qBittorrent to download
- After download, video files are in the mounted folder
Watching Movies
Infuse
- I
used tono longer use Infuse for watching movies. It accesses files on the server via SMB and watches them locally. - Advantages: Uses local hardware decoding, reducing mini PC pressure
- Disadvantages: Weaker scraping than Jellyfin, no account management, no watch progress tracking
- Only works on Apple devices: iPhone, iPad, MacBook, or Apple TV
Jellyfin
-
Jellyfin benefits from strong scraping capabilities, displaying complete movie posters, actor information, and supporting multiple accounts with separate watch histories.
-
The first time I used Jellyfin, movies were very laggy. I thought it needed a dedicated GPU, but later found that the N100 mini PC’s integrated graphics were more than sufficient. Use the
nyanmisaka/jellyfin:latestimage. -
To add Jellyfin, first create a
jellyfinfolder in theconfigdirectory, then modifydocker-compose.ymlto add:jellyfin: image: nyanmisaka/jellyfin:latest container_name: jellyfin user: "1000:1000" environment: - TZ=America/Toronto volumes: - ./config/jellyfin:/config - ./library:/media ports: - "8096:8096" networks: - media restart: unless-stopped -
Afterward, in Sonarr / Radarr’s Settings β Connections, you can add Jellyfin. The main purpose is to notify Jellyfin to refresh after downloads complete.
-
Once configured, the entire workflow is complete. In Radarr: search for a movie β click β qBittorrent downloads the movie in the background, Bazarr downloads subtitles automatically β after completion, Jellyfin is notified. Open Jellyfin directly to watch. Sometimes this process feels tedious, having to switch between different apps. Is there a way to integrate Sonarr and Radarr? Yes, Jellyseerr.
Jellyseerr
-
I think Jellyseerr has a very beautiful frontend. Its main function is integrating TV shows and movies. Users click and it calls Radarr and Sonarr to download. Radarr and Sonarr are Jellyseerr’s backend.
-
By this time, I had switched to k3s for deployment. Here’s the Jellyseerr deployment file:
apiVersion: v1 kind: Service metadata: name: jellyseerr namespace: media spec: ports: - name: http port: 5055 targetPort: 5055 protocol: TCP selector: app: jellyseerr --- apiVersion: apps/v1 kind: Deployment metadata: name: jellyseerr namespace: media spec: replicas: 1 selector: matchLabels: app: jellyseerr strategy: type: Recreate template: metadata: labels: app: jellyseerr spec: containers: - name: jellyseerr image: fallenbagel/jellyseerr:latest env: - name: LOG_LEVEL value: "info" - name: TZ value: "America/Toronto" - name: PUID value: "1000" - name: PGID value: "1000" ports: - containerPort: 5055 livenessProbe: httpGet: path: /status port: 5055 initialDelaySeconds: 10 periodSeconds: 30 readinessProbe: httpGet: path: /status port: 5055 initialDelaySeconds: 10 periodSeconds: 30 resources: limits: memory: "1Gi" requests: memory: "256Mi" volumeMounts: - name: config mountPath: /app/config volumes: - name: config hostPath: path: /home/kyxie/k3s/apps/media/config/jellyseerr type: DirectoryOrCreate
Jellyfin Media Player
-
Browser decoding performance is inferior to local decoding. You can install
jellyfin-media-playeron your computer for better performance using local decoding. -
On MacBook, use HomeBrew:
brew install --cask jellyfin-media-playerNote: This is not the Jellyfin Mobile app from the App Store.