# ============================================================================ # Shrinkerr — portable, multi-arch, CPU-encoding image. # # Runs on: # - linux/amd64 (x86_64 servers, Docker Desktop on Intel/AMD, etc.) # - linux/arm64 (Apple Silicon, Raspberry Pi 5, Ampere cloud hosts, …) # # Encoding: libx265 on the CPU. This image contains NO CUDA runtime — it's # deliberately small and portable. If your host has an NVIDIA GPU and you # want NVENC (hardware) encoding, pull the companion image instead: # ghcr.io//shrinkerr:nvenc (built from Dockerfile.nvenc) # Shrinkerr's runtime capability detection transparently falls back to # libx265 in this image; the Monitor page shows "Using CPU encoding". # ============================================================================ # Stage 1: build the frontend bundle. # Pinning --platform=$BUILDPLATFORM makes sure buildx does the npm step on # the native architecture of the builder host (the runner, not the target), # so cross-compilation for arm64 doesn't re-run npm inside QEMU. The output # is pure JS/CSS, so it's portable across target architectures. FROM --platform=$BUILDPLATFORM mirror.gcr.io/library/node:20-slim AS frontend-build WORKDIR /app/frontend COPY frontend/package.json frontend/package-lock.json* ./ RUN npm ci COPY frontend/ ./ RUN npm run build # Stage 2: runtime image. # python:3.11-slim-bookworm is multi-arch (amd64 + arm64) and much smaller # than the ubuntu/CUDA image used by the :nvenc variant. # # v0.7.4: pulled from Google's mirror.gcr.io instead of docker.io to avoid # Docker Hub anonymous + authenticated pull rate limits (200/6h on free) # from cooking the CI matrix (4 variants × 2 archs × multi-stage). The # mirror is a byte-for-byte proxy of Docker Hub's `library/*` namespace, # requires no auth, and has no rate limit. Same applies to the node:20-slim # pull in stage 1 above, and to Dockerfile.nvenc. FROM mirror.gcr.io/library/python:3.11-slim-bookworm ARG TARGETARCH # curl + xz-utils only needed for the ffmpeg download step below; purged at # the end of that step so they don't bloat the final image layer. # # VA-API runtime libraries (~30 MB total) let ffmpeg's hevc_qsv (Intel # Quick Sync) and hevc_vaapi (Intel + AMD generic VA-API) actually run # when the host passes /dev/dri through. Harmless on hosts without an # Intel/AMD GPU — ffmpeg simply doesn't surface those encoders. v0.3.67+. # # Cross-arch note (v0.3.71): the Intel iHD + i965 drivers are AMD64-only # in Debian (Intel iGPUs don't exist on ARM, and there's no upstream arm64 # build of `intel-media-va-driver-non-free`). Without this gate the v0.3.67 # build broke the multi-arch publish for several releases — the arm64 leg # failed with `Unable to locate package intel-media-va-driver-non-free`. # The arm64 leg keeps the arch-agnostic packages (libva, mesa-va-drivers, # vainfo) which are still useful for AMD GPUs reachable on arm64 hosts # (rare but not impossible — Ampere + Radeon, Pi + USB-C eGPU, etc.). # # libva2 + libva-drm2 → core VA-API runtime (multi-arch) # mesa-va-drivers → AMD radeonsi (multi-arch) # vainfo → diagnostic (multi-arch) # intel-media-va-driver-non-free → Intel iHD (Gen9+: Skylake / Arc) — AMD64 ONLY # i965-va-driver → Intel legacy (Gen8 and older) — AMD64 ONLY # # `intel-media-va-driver-non-free` lives in Debian's `non-free` component; # enabled below before the conditional install. Source-open driver, # "non-free" naming is historical from when it shipped as a binary blob. RUN set -eux; \ apt-get update; \ # v0.5.13: pull latest security patches at build time. Halves the # High/Medium CVE count Docker Scout reports without changing the # base image. Cheap and low-risk — security updates are minor # bumps within the bookworm release line. apt-get upgrade -y --no-install-recommends; \ apt-get install -y --no-install-recommends curl xz-utils libarchive-tools libbluray-bin mkvtoolnix \ tesseract-ocr tesseract-ocr-eng tesseract-ocr-chi-sim tesseract-ocr-chi-tra tesseract-ocr-jpn tesseract-ocr-kor tesseract-ocr-rus tesseract-ocr-ara \ libgl1 libglib2.0-0; \ if [ "${TARGETARCH}" = "amd64" ]; then \ # All VA-API packages (runtime libs + Intel iHD + AMD radeonsi + # diagnostic) only on amd64. arm64 builds keep the minimal # pre-v0.3.67 install since Intel iGPUs are x86-only and AMD # GPUs on arm64 hosts are exceptionally rare. # # Enable non-free + non-free-firmware components by EDITING the # existing DEB822 sources file (python:3.11-slim-bookworm uses # `/etc/apt/sources.list.d/debian.sources`) rather than dropping # a second `.list`. Adding a parallel `bookworm` repo without a # `Signed-By:` directive makes apt fail with # E: Conflicting values set for option Signed-By regarding # source http://deb.debian.org/debian/ bookworm: # /usr/share/keyrings/debian-archive-keyring.gpg != # which is what bit v0.3.67 → v0.3.73. The sed targets both the # `bookworm` and `bookworm-security` stanzas in the same file # (one substitution per Components line). v0.3.74+. sed -i 's|^Components: main$|Components: main contrib non-free non-free-firmware|' \ /etc/apt/sources.list.d/debian.sources; \ apt-get update; \ apt-get install -y --no-install-recommends \ libva-drm2 \ mesa-va-drivers \ vainfo \ intel-media-va-driver-non-free \ i965-va-driver \ libvpl2 libmfx1 \ build-essential cmake meson ninja-build pkg-config \ libdrm-dev libigdgmm-dev libpciaccess-dev bzip2; \ # libvpl2: Intel oneVPL dispatcher. libmfx1: legacy MediaSDK # runtime — kept as a fallback for older HW; the modern # libmfx-gen runtime built below takes precedence on Gen9+. # build-essential + cmake + meson + ninja + dev headers: # temporarily installed for the libva and vpl-gpu-rt builds # below. Purged at the end of the layer. # # === libva 2.22 from source (v0.3.88+) ============================ # # The BtbN n7.x ffmpeg static builds are compiled against # libva ≥ 2.20 (which introduced `vaMapBuffer2`). Debian # bookworm ships libva 2.17 even via backports — every apt- # installable libva on bookworm is too old, and the runtime # symbol lookup for `vaMapBuffer2` fails the first time # hevc_vaapi tries to encode. We replace the system libva with # an upstream 2.22.x build. libva 2.x is ABI-stable, so the # iHD / i965 / mesa drivers (compiled against 2.17) continue # to work against 2.22 without rebuilding. ~30s build. LIBVA_VER=2.22.0; \ curl -fsSL "https://github.com/intel/libva/releases/download/${LIBVA_VER}/libva-${LIBVA_VER}.tar.bz2" -o /tmp/libva.tar.bz2; \ mkdir -p /tmp/libva-src; \ tar xjf /tmp/libva.tar.bz2 -C /tmp/libva-src --strip-components=1; \ cd /tmp/libva-src; \ meson setup build --prefix=/usr -Dlibdir=lib/x86_64-linux-gnu --buildtype=release; \ ninja -C build install; \ cd /; rm -rf /tmp/libva-src /tmp/libva.tar.bz2; \ ldconfig; \ # # === vpl-gpu-rt (libmfx-gen) from source (v0.3.89+) =============== # # Intel's modern oneVPL GPU runtime — what Sonarr/Radarr-tier # users on current Ubuntu/Arch get from `libmfx-gen1` package. # Not packaged for Debian 12 or Ubuntu 22.04. Without this, # libvpl2 (dispatcher) falls back to legacy libmfx1, which # rejects libva-2.22 VADisplays with `MFX_ERR_INCOMPATIBLE_- # VIDEO_PARAM (-17)` — that's what bit users testing v0.3.84 # → v0.3.88. With libmfx-gen.so.1.2 installed, the dispatcher # finds the modern runtime first and QSV works against modern # libva + iHD drivers. ~3 min build on x86_64. VPL_VER=24.4.4; \ curl -fsSL "https://github.com/intel/vpl-gpu-rt/archive/refs/tags/intel-onevpl-${VPL_VER}.tar.gz" -o /tmp/vpl.tar.gz; \ mkdir -p /tmp/vpl-src; \ tar xzf /tmp/vpl.tar.gz -C /tmp/vpl-src --strip-components=1; \ cd /tmp/vpl-src; \ # Memory-conservative configure (v0.3.92+). The default # `cmake --build --parallel` uses every core, and a # multi-arch buildx build runs both leg's compile/link # steps concurrently — easy way to exceed 7 GB on GHA # free runners and OOM-kill the runner mid-link # (lost-communication errors at the mfx_common_hw stage). # Mitigations: # MinSizeRel + IPO=OFF → smaller object files, less # template-instantiation RAM # BUILD_TESTS=OFF → skip test-suite compilation # --parallel 1 → serialize compile + link; # slower (~10 min vs ~5 min) # but stays under the budget cmake -B build \ -DCMAKE_BUILD_TYPE=MinSizeRel \ -DCMAKE_INSTALL_PREFIX=/usr \ -DCMAKE_INSTALL_LIBDIR=lib/x86_64-linux-gnu \ -DCMAKE_INTERPROCEDURAL_OPTIMIZATION=OFF \ -DBUILD_TESTS=OFF; \ cmake --build build --parallel 1; \ cmake --install build; \ cd /; rm -rf /tmp/vpl-src /tmp/vpl.tar.gz; \ ldconfig; \ # # Purge build toolchain — keep the image lean. The runtime libs # (libva-drm2, mesa-va-drivers, libvpl2, libmfx1, …) stay # because they were explicitly installed above; apt sees them as # user-installed and won't auto-remove them. apt-get purge -y build-essential cmake meson ninja-build pkg-config libdrm-dev libigdgmm-dev libpciaccess-dev bzip2; \ apt-get autoremove -y; \ # # === Intel media driver 25.2.4 (v0.9.160) ========================== # # Bookworm's iHD driver (23.1) is too old for Arc / Flex cards (DG2, # ATS-M): vaInitialize fails with "iHD_drv_video.so init failed" # (GitHub issue, Flex 170). Intel's own build from its Ubuntu 22.04 # repo needs only glibc 2.34, runs on bookworm, is built for libva # 2.22 (the one above) and supports Broadwell through Battlemage. # Pinned by version and hash; it replaces bookworm's driver + gmmlib. INTEL_POOL=https://repositories.intel.com/gpu/ubuntu/pool/unified/i; \ curl -fsSL --retry 5 --retry-delay 3 -o /tmp/libigdgmm12.deb "${INTEL_POOL}/intel-gmmlib/libigdgmm12_22.7.2-1135~22.04_amd64.deb"; \ curl -fsSL --retry 5 --retry-delay 3 -o /tmp/intel-media.deb "${INTEL_POOL}/intel-media-driver-non-free/intel-media-va-driver-non-free_25.2.4-1146~22.04_amd64.deb"; \ printf '%s %s\n' \ c8b3dfbc044735e9502fd4b8e9560f9118afd49755f8caa4d173b0a84044638d /tmp/libigdgmm12.deb \ fa22334f9431985f2b09748c365e61f54ff7b9b2a01a9c6d1cf24c1075a315fc /tmp/intel-media.deb \ | sha256sum -c -; \ apt-get install -y --no-install-recommends /tmp/libigdgmm12.deb /tmp/intel-media.deb; \ rm -f /tmp/libigdgmm12.deb /tmp/intel-media.deb; \ else \ echo "Skipping VA-API runtime on ${TARGETARCH} (amd64-only in this image)"; \ fi; \ rm -rf /var/lib/apt/lists/* # Install ffmpeg static build from BtbN (includes libx265, libvmaf, x264). # BtbN publishes per-architecture assets under the rolling `latest` release; # asset filenames differ between master and tagged (nX.Y) releases. # master: ffmpeg-master-latest-{linux64,linuxarm64}-gpl.tar.xz # nX.Y: ffmpeg-nX.Y-latest-{linux64,linuxarm64}-gpl-X.Y.tar.xz ARG FFMPEG_BUILD=n7.1 RUN set -e; \ case "${TARGETARCH}" in \ amd64) ARCH_TAG="linux64" ;; \ arm64) ARCH_TAG="linuxarm64" ;; \ *) echo "Unsupported TARGETARCH: ${TARGETARCH}" >&2; exit 2 ;; \ esac; \ case "${FFMPEG_BUILD}" in \ master) FF_URL="https://github.com/BtbN/FFmpeg-Builds/releases/download/latest/ffmpeg-master-latest-${ARCH_TAG}-gpl.tar.xz" ;; \ n*) FF_VER="${FFMPEG_BUILD#n}"; \ FF_URL="https://github.com/BtbN/FFmpeg-Builds/releases/download/latest/ffmpeg-${FFMPEG_BUILD}-latest-${ARCH_TAG}-gpl-${FF_VER}.tar.xz" ;; \ *) echo "Unknown FFMPEG_BUILD: ${FFMPEG_BUILD} (expected 'master' or 'nX.Y')" >&2; exit 2 ;; \ esac; \ echo "Installing ffmpeg ${FFMPEG_BUILD} for ${TARGETARCH}: ${FF_URL}"; \ # v0.9.105: download to a file with retries, THEN extract. Piping curl # straight into tar can't retry a mid-stream cut (tar has already consumed # a partial stream) — that flaked the arm64 leg under slow QEMU emulation. curl -fsSL --retry 5 --retry-delay 3 --retry-all-errors "${FF_URL}" -o /tmp/ffmpeg.tar.xz; \ tar -xJf /tmp/ffmpeg.tar.xz --strip-components=2 -C /usr/local/bin/ --wildcards '*/bin/ffmpeg' '*/bin/ffprobe'; \ rm -f /tmp/ffmpeg.tar.xz; \ chmod +x /usr/local/bin/ffmpeg /usr/local/bin/ffprobe; \ apt-get purge -y curl xz-utils && apt-get autoremove -y; \ rm -rf /var/lib/apt/lists/*; \ echo "ffmpeg installed:"; ffmpeg -version 2>&1 | head -1; \ echo "VMAF filter:"; (ffmpeg -filters 2>&1 | grep libvmaf || echo "NOT FOUND") # Record lineage so the running backend's Monitor page knows exactly which # ffmpeg build the image shipped with. ENV SHRINKERR_FFMPEG_BUILD=${FFMPEG_BUILD} # This image has no CUDA runtime, so NVENC is structurally unavailable. # Backend detection (backend/nodes.py) will notice no NVIDIA GPU + no # nvidia-smi and advertise only libx265. Flag it here too so the UI can # explain "this image is CPU-only by design" rather than "your driver's # too old" to users pulling :latest without realising :nvenc exists. ENV SHRINKERR_CPU_ONLY_IMAGE=1 WORKDIR /app COPY requirements.txt . RUN pip3 install --no-cache-dir -r requirements.txt COPY backend/ backend/ COPY VERSION . # CHANGELOG is read at runtime by /api/stats/changelog so the Updates # section in Settings can show release notes without a network call. COPY CHANGELOG.md . COPY --from=frontend-build /app/frontend/dist frontend/dist RUN mkdir -p /app/data EXPOSE 6680 # Bundled TMDB non-commercial API key — passed in at build time from the # TMDB_API_KEY GitHub Actions secret so the image ships with a working # default. User-saved keys in Settings always win (see # backend/metadata.py::resolve_tmdb_key). Builds without the secret set # leave this empty and behave as before. ARG TMDB_API_KEY="" ENV SHRINKERR_TMDB_API_KEY=${TMDB_API_KEY} HEALTHCHECK --interval=30s --timeout=5s --start-period=60s --retries=3 \ CMD python3 -c "import urllib.request; urllib.request.urlopen('http://localhost:6680/api/health')" || exit 1 # SHRINKERR_MODE=worker → run as a remote worker node (no server, no UI). # Default → run the FastAPI server + UI. CMD ["python3", "-m", "backend.main"]