Engineering Article

Setting Up a Home Media Server with Docker: A Beginner's Guide

Setting up a home media server with Docker: a beginner’s guide

Managing disparate media files, manual download tracking, and playback across multiple devices leads to fragmented storage, conflicting dependencies, and file access permission errors across host operating systems. Bare-metal service installations complicate dependency isolation and increase administrative overhead when updating individual applications.

A containerized stack combining qBittorrent, Prowlarr, Sonarr, Radarr, and Jellyfin through Docker Compose consolidates storage management, isolates application dependencies, and automates media ingestion pipelines with reproducible volume bindings.

The problem and production context

Deploying media management and streaming applications directly onto a host system creates operational friction through library dependencies, permission collisions, and uncoordinated file access.

  • Failure scenario: A native download client writes files with host-specific user permissions that prevent downstream indexing services from moving, renaming, or importing files into the media library directories.
  • Why default approaches fall short: Running applications directly on the host operating system mixes runtime dependencies, requires separate manual service management, and lacks unified volume mapping across ingestion and streaming layers.
  • Production impact: Media streams fail because of unindexed files, service updates break shared runtime dependencies, and exposing unmanaged services directly without controlled network boundaries exposes administrative interfaces.

Consolidating media files on a dedicated system requires automated download categorization and library organization workflows without complex host software installations.

Mental model and core concepts

Operating an automated home media pipeline requires understanding how independent containers coordinate through shared filesystems and network ports.

1. Ingestion and indexing pipeline

Media acquisition operates as a coordinated workflow across specialized services:

  • Prowlarr manages indexers and synchronizes tracker configurations to Sonarr and Radarr.
  • Sonarr and Radarr track series and movies, monitor releases, and send download commands to the download client.
  • qBittorrent downloads files to a staging volume.
  • Sonarr and Radarr detect download completion, hardlink or move files to the final library directories, and notify the streaming server.
  • Jellyfin reads the library directories and streams transcoded media to client devices.

2. File system hierarchy and permissions

Services share host directories through volume bindings. Consistent user identifiers (PUID=1000) and group identifiers (PGID=1000) across all containers ensure that every service reads and writes files with matching POSIX permissions.

The persistent directory layout isolates application state from large media payloads:

mkdir -p ~/media-server/{configs,downloads,movies,tv}
cd ~/media-server

3. Containerized networking and interface binding

Each container exposes its administrative web interface through mapped host ports while communicating internally across Docker bridge networks:

  • qBittorrent Web UI: http://<server-ip>:8080
  • Prowlarr Web UI: http://<server-ip>:9696
  • Sonarr Web UI: http://<server-ip>:8989
  • Radarr Web UI: http://<server-ip>:7878
  • Jellyfin Web UI: http://<server-ip>:8096

Production implementation

The following Compose specification deploys the complete media service stack with shared volume paths and standardized permission environments.

services:
  qbittorrent:
    image: lscr.io/linuxserver/qbittorrent:latest
    container_name: qbittorrent
    ports:
      - "8080:8080"
    volumes:
      - ./configs/qbittorrent:/config
      - ./downloads:/downloads
    environment:
      - PUID=1000
      - PGID=1000
    restart: unless-stopped

  prowlarr:
    image: lscr.io/linuxserver/prowlarr:latest
    container_name: prowlarr
    ports:
      - "9696:9696"
    volumes:
      - ./configs/prowlarr:/config
    environment:
      - PUID=1000
      - PGID=1000
    restart: unless-stopped

  sonarr:
    image: lscr.io/linuxserver/sonarr:latest
    container_name: sonarr
    ports:
      - "8989:8989"
    volumes:
      - ./configs/sonarr:/config
      - ./downloads:/downloads
      - ./tv:/tv
    environment:
      - PUID=1000
      - PGID=1000
    restart: unless-stopped

  radarr:
    image: lscr.io/linuxserver/radarr:latest
    container_name: radarr
    ports:
      - "7878:7878"
    volumes:
      - ./configs/radarr:/config
      - ./downloads:/downloads
      - ./movies:/movies
    environment:
      - PUID=1000
      - PGID=1000
    restart: unless-stopped

  jellyfin:
    image: lscr.io/linuxserver/jellyfin:latest
    container_name: jellyfin
    ports:
      - "8096:8096"
    volumes:
      - ./configs/jellyfin:/config
      - ./movies:/media/movies
      - ./tv:/media/tv
    environment:
      - PUID=1000
      - PGID=1000
    restart: unless-stopped

Launch the stack in detached mode:

docker compose up -d

Verify container health and review runtime logs:

docker compose ps
docker compose logs --tail=50

Architectural trade-offs and edge cases

Containerizing media pipelines introduces architectural trade-offs between filesystem performance, storage isolation, and network routing.

  • Latency versus consistency: Cross-volume imports across separate Docker volume mounts force physical file copy operations instead of instantaneous atomic filesystem hardlinks, increasing disk I/O latency and duplicating storage usage during import operations.
  • Failure recovery: The restart: unless-stopped policy ensures automatic service recovery after host reboots or container crashes. Interrupted torrent downloads or incomplete file moves require internal SQLite database transaction recovery.
  • Scale limitations: Single-host Docker Compose deployments are constrained by host disk I/O throughput and CPU transcoding capacity. When simultaneous video transcodes saturate hardware limits, transcoding queues degrade stream playback.

Common anti-patterns and gotchas

  • Inconsistent volume bindings: Mounting /downloads and /movies as separate filesystems inside containers prevents hardlinks, causing Sonarr and Radarr to execute slow I/O copy operations instead of instant inode links. Standardize root storage paths across containers.
  • Omitted root media paths: Failing to define valid root paths within Sonarr or Radarr UI settings causes import tasks to fail silently after download completion. Always configure and verify root directories in application settings.
  • Unchanged default administrative credentials: Leaving default passwords active on web interfaces permits unauthorized control on the local network. Set strong unique passwords immediately upon first login.
  • Untagged image upgrades: Relying on :latest tags without version tracking introduces breaking schema changes during uncoordinated image pulls. Pin specific image versions when upgrading production stacks.
  • Public internet exposure without TLS or authentication: Exposing container ports directly through router port forwarding exposes administrative APIs to external scans. Route external traffic through a reverse proxy with SSL/TLS encryption and authentication layers.

Implementation checklist

  1. Confirm host machine runs a supported Linux environment, NAS, or mini PC with Docker and Docker Compose installed.
  2. Format and mount dedicated storage with write permissions assigned to user identifier 1000 and group 1000.
  3. Create the directory tree ~/media-server/{configs,downloads,movies,tv}.
  4. Deploy the docker-compose.yml manifest using docker compose up -d.
  5. Verify container states with docker compose ps and check logs using docker compose logs --tail=50.
  6. Access web interfaces across ports 8080, 9696, 8989, 7878, and 8096, changing default passwords immediately.
  7. Add indexers in Prowlarr and synchronize them to Sonarr and Radarr.
  8. Configure qBittorrent as the download client in Sonarr and Radarr.
  9. Configure root media paths in Sonarr (/tv) and Radarr (/movies).
  10. Map library directories (/media/movies, /media/tv) inside Jellyfin and verify media indexing.
  11. Configure a reverse proxy with HTTPS for encrypted local and remote access.
  12. Schedule automated backups for ./configs directories and internal application databases.
  13. Integrate Bazarr to automate subtitle acquisition.
  14. Set up container update monitoring using tools such as Watchtower.