Prosumer Edge Media Hub
This repository contains the Infrastructure-as-Code (IaC) for deploying a self-hosted media stack on an Intel N100 Mini-PC running Debian 13 "Trixie". The architecture emphasizes zero-trust micro-segmentation, atomic hardlinks for media management, and hardware-accelerated transcoding.
Table of Contents
Architecture Overview
This project implements a robust, spec-driven media server solution designed for resilience and efficient media processing. It leverages Docker Compose for service orchestration and Ansible for host provisioning, adhering to strict architectural and security guidelines.
Technology Stack
- Operating System: Debian 13 "Trixie" (The Host)
- Hardware: Intel N100 Mini-PC (Intel QuickSync supported)
- Container Engine: Docker
- Orchestration: Docker Compose
- Provisioning: Ansible
- Firewall: UFW (Uncomplicated Firewall)
- Ingress: Cloudflare Tunnel (Zero-Trust)
Core Principles
- Spec-Driven Development: All infrastructure is defined and implemented based on detailed specifications (
CONSTITUTION.md, PRODUCT_SPECIFICATION.md, ARCHITECTURE.md).
- Idempotency: All deployment scripts and configurations are idempotent, ensuring consistent state regardless of how many times they are executed.
- Zero Root Execution: Docker containers never run as root, utilizing a dedicated
mediasvc system user with UID/GID 5000.
- State vs. Compute Isolation: Strict separation between stateless compute (Docker containers) and stateful data (volume mounts). All state lives in
/opt/mediastack/appdata/.
- Zero-Trust Micro-segmentation: Network traffic is strictly controlled between services using isolated bridge networks.
- No Port Forwarding: All external access is routed through a secure Cloudflare Tunnel.
Components
The media stack consists of the following services, categorized by their Bounded Contexts:
- Delivery:
- Jellyfin: Media server with Intel QuickSync hardware transcoding.
- Acquisition:
- Radarr: Movie collection manager.
- Sonarr: TV show collection manager.
- Prowlarr: Indexer manager for Usenet/Torrents.
- Recyclarr: Automatically syncs TRaSH Guides quality profiles.
- Processing:
- SABnzbd: Usenet download client (Resource limited to 2 CPUs / 2GB RAM).
- Tdarr: Automated media transcoding (Intel QuickSync on-host).
- On-Demand GPU Workload Offloading: A transient laptop node with an AMD Radeon RX 7600M XT can join the Tdarr cluster over NFSv4 + the Tdarr control plane to offload GPU encoding on demand. The export and control port are strictly scoped to the auto-detected LAN subnet via UFW with zero-trust UID/GID squashing (see Distributed Tdarr Node).
- Media Request & Identity & Access:
- Seerr: UI for media discovery and requests.
- Ingress:
- Cloudflared: Establish secure tunnel for zero-trust external access.
- Dashboards & Maintenance:
- Homepage: Centralized dashboard for all services.
- Watchtower: Automatic Docker image updates.
- Docker Socket Proxy: Secure abstraction for the Docker socket.
- Maintainerr: Rule-based cleanup of stale media from Jellyfin, the *arrs, and Seerr.
- Entertainment:
- SingPulse: House-party karaoke: guests queue songs from their phones, The Stage (Android TV) plays them (Resource limited to 2 CPUs / 1GB RAM). See SingPulse.
- Observability:
- Dozzle: Real-time container log streaming (e.g. Watchtower update runs) via the constrained Docker socket proxy, with an authenticated MCP endpoint (
/api/mcp) exposing read-only container tools to AI coding agents.
File System Layout
The system mandates a single root directory (/opt/mediastack/) for all media data to enable Atomic Hardlinks (instant, zero-space moves).
/opt/mediastack/
├── docker-compose.yml
├── .env
├── appdata/ <-- Config state (Must reside on fast SSD)
│ ├── jellyfin/
│ ├── radarr/
│ └── ...
└── data/ <-- The Media Payload (Resides on High-Capacity Drive)
├── usenet/ <-- SABnzbd active downloads
└── media/
├── movies/ <-- Final destination for Radarr
└── tv/ <-- Final destination for Sonarr
Getting Started
1. Preparing The Host
Before running Ansible, the Intel N100 Mini-PC must be manually prepared:
This is the beginning of the README. Read the rest at the original. Read the full README ↗