diff --git a/Dockerfile b/Dockerfile index 64ea4e2..7bb1416 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,8 @@ # ---- Build stage ---- -FROM golang:1.25-alpine AS builder +# Pin the builder to the host's native arch and cross-compile (CGO is off, so +# Go cross-compiles trivially). During multi-arch buildx this keeps `go build` +# at native speed instead of compiling under QEMU emulation for the foreign arch. +FROM --platform=$BUILDPLATFORM golang:1.25-alpine AS builder RUN apk add --no-cache git ca-certificates @@ -13,34 +16,63 @@ RUN go mod download COPY . . ARG VERSION=dev -RUN CGO_ENABLED=0 go build -ldflags="-s -w -X github.com/torrentclaw/unarr/internal/cmd.Version=${VERSION}" -trimpath -o /unarr ./cmd/unarr/ +ARG TARGETOS +ARG TARGETARCH +RUN CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -ldflags="-s -w -X github.com/torrentclaw/unarr/internal/cmd.Version=${VERSION}" -trimpath -o /unarr ./cmd/unarr/ # ---- Runtime stage ---- -FROM alpine:3.22 +# glibc base (not Alpine/musl). NVIDIA's userspace — nvidia-smi and the +# libnvidia-encode / libcuda libs that `--gpus all` injects, plus the static +# BtbN ffmpeg that links nvenc — are all glibc ELF. On musl they fail with +# "no such file or directory" (missing glibc loader), so HW transcode is +# impossible on Alpine. bookworm-slim is the smallest base that runs the full +# NVIDIA stack while still falling back to software libx264 when no GPU is +# passed in. +FROM debian:bookworm-slim -# Use Alpine's native musl ffmpeg + ffprobe instead of the johnvansickle / -# BtbN static glibc builds — those need a glibc shim on Alpine and the -# vector-math symbols the GPL builds reference are not satisfiable by -# gcompat. Alpine ships ffmpeg ~7.x which is fine for the HLS transcoding -# pipeline (libx264 + libfdk-aac alternatives included). -RUN apk upgrade --no-cache && \ - apk add --no-cache ca-certificates tzdata ffmpeg wget +# par2 → repair corrupted Usenet segments (without it a single bad segment +# silently corrupts the output). +# 7z → archive extractor for RAR/7z-packed downloads (p7zip-full also reads +# RAR5, so unrar — unavailable as a free Debian package — isn't needed). +# tzdata/ca-certificates → TLS + correct local time for schedules/logs. +RUN apt-get update && \ + apt-get install -y --no-install-recommends \ + ca-certificates tzdata wget xz-utils par2 p7zip-full && \ + rm -rf /var/lib/apt/lists/* + +# TARGETARCH is set automatically by Docker buildx during cross-builds. +ARG TARGETARCH=amd64 + +# Static GPL ffmpeg + ffprobe with nvenc compiled in (BtbN builds). nvenc is +# linked but the actual libnvidia-encode.so is dlopen'd at runtime from the +# host driver that `--gpus all` exposes — so the same binary does HW transcode +# when a GPU is present and falls back to libx264 when it isn't. Placed in +# /usr/local/bin so ResolveFFmpeg picks them up off PATH ahead of any distro +# ffmpeg. arm64 has no nvenc but the build still serves software transcode. +RUN case "$TARGETARCH" in \ + amd64) FF_ARCH=linux64 ;; \ + arm64) FF_ARCH=linuxarm64 ;; \ + *) echo "unsupported TARGETARCH=$TARGETARCH" >&2; exit 1 ;; \ + esac && \ + wget -4 --tries=3 --timeout=30 -qO /tmp/ffmpeg.tar.xz "https://github.com/BtbN/FFmpeg-Builds/releases/download/latest/ffmpeg-master-latest-${FF_ARCH}-gpl.tar.xz" && \ + mkdir -p /tmp/ff && tar -xJf /tmp/ffmpeg.tar.xz -C /tmp/ff --strip-components=1 && \ + cp /tmp/ff/bin/ffmpeg /tmp/ff/bin/ffprobe /usr/local/bin/ && \ + chmod +x /usr/local/bin/ffmpeg /usr/local/bin/ffprobe && \ + rm -rf /tmp/ffmpeg.tar.xz /tmp/ff # Bundle cloudflared so `unarr funnel on` (default: on, see config defaults) # Just Works on a headless container with no first-run network round-trip. -# TARGETARCH is set automatically by Docker buildx during cross-builds. -ARG TARGETARCH=amd64 RUN case "$TARGETARCH" in \ amd64) CF_ARCH=amd64 ;; \ arm64) CF_ARCH=arm64 ;; \ arm) CF_ARCH=armhf ;; \ *) echo "unsupported TARGETARCH=$TARGETARCH" >&2; exit 1 ;; \ esac && \ - wget -qO /usr/local/bin/cloudflared "https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-$CF_ARCH" && \ + wget -4 --tries=3 --timeout=30 -qO /usr/local/bin/cloudflared "https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-$CF_ARCH" && \ chmod +x /usr/local/bin/cloudflared # Non-root user (UID 1000 matches typical host user for volume permissions) -RUN addgroup -g 1000 unarr && adduser -u 1000 -G unarr -D -h /home/unarr unarr +RUN groupadd -g 1000 unarr && useradd -u 1000 -g 1000 -m -d /home/unarr unarr # Default directories RUN mkdir -p /config /downloads /data && \ @@ -55,6 +87,13 @@ ENV UNARR_CONFIG_DIR=/config ENV UNARR_DOWNLOAD_DIR=/downloads ENV XDG_DATA_HOME=/data +# NVIDIA passthrough defaults. `--gpus all` alone only grants the "utility" + +# "compute" capabilities; nvenc needs "video". Baking these here means a plain +# `docker run --gpus all` (or the compose device reservation) lights up HW +# transcode with zero extra flags. Harmless when no GPU is attached. +ENV NVIDIA_VISIBLE_DEVICES=all +ENV NVIDIA_DRIVER_CAPABILITIES=video,compute,utility + VOLUME ["/config", "/downloads", "/data"] ENTRYPOINT ["unarr"] diff --git a/docker-compose.yml b/docker-compose.yml index 8e0b32e..60446db 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -45,9 +45,21 @@ services: # Named volume keeps this off your media drive (avoids NFS locking issues). - unarr-data:/data - # Optional: limit CPU/RAM for transcoding on shared hosts + # --- NVIDIA GPU: hardware transcode (nvenc) --- + # Uncomment on a host with an NVIDIA GPU + nvidia-container-toolkit. The + # image already bundles an nvenc-enabled ffmpeg and sets + # NVIDIA_DRIVER_CAPABILITIES=video,compute,utility, so this device + # reservation is the only thing needed to enable HW transcode. Without a GPU + # the same image falls back to software (libx264) automatically — leave it + # commented. (docker run equivalent: add --gpus all) # deploy: # resources: + # reservations: + # devices: + # - driver: nvidia + # count: all + # capabilities: [gpu] + # # Optional: cap CPU/RAM for transcoding on shared hosts # limits: # memory: 2G # cpus: "4.0"