Ctrl K
ComunidadGuías…

Dockerfile

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# ---- Build stage ----
# 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.
# Must match the go directive in go.mod: the image runs GOTOOLCHAIN=local, so an
# older builder fails `go mod download` (v1.11.10 shipped without an image for
# exactly this). CI's "Dockerfile Go matches go.mod" step enforces it.
FROM --platform=$BUILDPLATFORM golang:1.26-alpine AS builder

RUN apk add --no-cache git ca-certificates

WORKDIR /src

# Copy go.mod/go.sum first for layer caching. third_party/ comes along because
# go.mod `replace`s anacrolix/torrent with ./third_party/torrent: without it
# `go mod download` cannot read that module's go.mod.
COPY go.mod go.sum ./
COPY third_party ./third_party
RUN go mod download

# Copy source
COPY . .

ARG VERSION=dev
ARG TARGETOS
ARG TARGETARCH
RUN CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -ldflags="-s -w -X github.com/Unarr-app/unarr-cli/internal/cmd.Version=${VERSION}" -trimpath -o /unarr ./cmd/unarr/

# ---- Runtime stage ----
# 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

# 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.
# libvulkan1 → the Vulkan loader (libvulkan.so.1). ffmpeg's libplacebo filter
#         (GPU HDR→SDR tonemap) loads Vulkan dynamically through it; without the
#         loader the filter can't reach a GPU even when the NVIDIA driver mounts
#         its ICD. ~150 KB. The agent only USES libplacebo after a functional
#         probe (FFmpegSupportsLibplacebo) succeeds AND a real HW encoder is
#         present, so this is inert on hosts without a working Vulkan GPU.
#
#         NOTE: in this container libplacebo's Vulkan probe ALWAYS fails and the
#         agent falls back to the CPU zscale tonemap chain — by design, not a
#         bug. The nvidia Vulkan ICD is libGLX_nvidia.so.0, whose GL backend
#         (libnvidia-glcore) references glibc malloc hooks removed in glibc 2.34
#         (__malloc_hook/__free_hook/...) and the Xorg symbol ErrorF; on a
#         headless modern-glibc base (debian or ubuntu) those go unresolved so
#         vkCreateInstance returns VK_ERROR_INCOMPATIBLE_DRIVER. We deliberately
#         do NOT chase it (would need `graphics` cap + X11 libs + a 1.4 loader
#         AND a desktop-class glibc/Xorg — fragile, distro+driver coupled). The
#         loader stays so that on the RARE host where Vulkan does come up the
#         probe can use it. nvenc/nvdec (CUDA, not Vulkan) work regardless.
#         GPU HDR tonemap is a bare-metal-binary feature, not a container one.
# libdrm2/libva2/libva-drm2 → VAAPI/QSV userspace the bundled BtbN ffmpeg
#         dlopen()s for Intel QuickSync (h264_qsv/hevc_qsv). Without libdrm.so.2
#         the qsv encoder aborts on the very first frame ("libdrm.so.2: cannot
#         open shared object file" → `load_library: Assertion 0` → core dump), so
#         an Intel-GPU host (renderD128) never produces a segment and web
#         playback hangs forever on init.mp4. All three exist on arm64 too and
#         are inert there, so they live on the common line; the Intel-only driver
#         + oneVPL runtime are installed in an amd64-guarded block below.
# (No gosu: docker-entrypoint.sh drops root with setpriv from util-linux, which
#  is Essential in Debian and keeps the supplementary groups gosu discarded.)
RUN apt-get update && \
    apt-get install -y --no-install-recommends \
      ca-certificates tzdata wget xz-utils par2 p7zip-full libvulkan1 \
      libdrm2 libva2 libva-drm2 && \
    rm -rf /var/lib/apt/lists/*

# Intel QuickSync (QSV) runtime — amd64 only. The oneVPL dispatcher (libvpl2) +
# the Gen GPU runtime (libmfx-gen1.2) are what the bundled ffmpeg loads for
# h264_qsv/hevc_qsv; intel-media-va-driver-non-free is the iHD VAAPI driver
# covering Gen9+ incl the Alder Lake-N SoCs that UGREEN / Intel NAS boxes ship.
# These packages have no arm64 candidate (Intel x86 media stack), so installing
# them on the common line would break the arm64 buildx leg — guard by arch. iHD
# lives in non-free, absent from bookworm-slim's default sources, so enable the
# component first. Inert on NVIDIA hosts (which use nvenc, not QSV).
# The non-free stanza reuses the base image's deb822 keyring (Signed-By); a plain
# one-line `deb ...` list for the same suite trips apt's "Conflicting values set
# for option Signed-By" on modern bookworm-slim (which ships /etc/apt/sources.list.d
# /debian.sources with the keyring pinned), so it must be deb822 with the same key.
RUN if [ "$(dpkg --print-architecture)" = "amd64" ]; then \
      printf 'Types: deb\nURIs: http://deb.debian.org/debian\nSuites: bookworm\nComponents: non-free non-free-firmware\nSigned-By: /usr/share/keyrings/debian-archive-keyring.gpg\n' \
        > /etc/apt/sources.list.d/non-free.sources && \
      apt-get update && \
      apt-get install -y --no-install-recommends \
        intel-media-va-driver-non-free libmfx-gen1.2 libvpl2 && \
      rm -rf /var/lib/apt/lists/*; \
    fi

# Arch for the bundled deps below is taken from `dpkg --print-architecture` (the
# real arch of THIS runtime stage), NOT the TARGETARCH build-arg. A baked
# `ARG TARGETARCH=amd64` default used to shadow buildx's per-leg value in this
# stage, so even the published arm64 image bundled an amd64 cloudflared/ffmpeg
# while the unarr binary was native arm64 → "exec format error" when the daemon
# spawned cloudflared → funnel never came up → TV/Stremio connect failed
# ("Failed to get add-on manifest"). dpkg reads the emulated base image's arch,
# so it is correct under buildx cross-builds AND a plain `docker build`.

# 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 ARCH="$(dpkg --print-architecture)" && \
    case "$ARCH" in \
      amd64) FF_ARCH=linux64 ;; \
      arm64) FF_ARCH=linuxarm64 ;; \
      *)     echo "unsupported arch=$ARCH" >&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.
# Keep CF_VERSION and the hashes in step with internal/funnel/install.go.
ARG CF_VERSION=2026.10.5
RUN ARCH="$(dpkg --print-architecture)" && \
    case "$ARCH" in \
      amd64)  CF_ARCH=amd64; CF_SHA=e1bc0cf084fd9e590554fe769a86b1db46701c3f847a43d902f7b974a4e16298 ;; \
      arm64)  CF_ARCH=arm64; CF_SHA=72e0d05a67e09da87cf495e409a28c3cc5994dd23fc66a982eebea6a55923b7d ;; \
      armhf)  CF_ARCH=armhf; CF_SHA=4aed2f4179f2ac1068b45073761409ba4540a631a2e70df98543a65b4a982d27 ;; \
      *)      echo "unsupported arch=$ARCH" >&2; exit 1 ;; \
    esac && \
    wget -4 --tries=3 --timeout=30 -qO /usr/local/bin/cloudflared "https://github.com/cloudflare/cloudflared/releases/download/$CF_VERSION/cloudflared-linux-$CF_ARCH" && \
    echo "$CF_SHA  /usr/local/bin/cloudflared" | sha256sum -c - && \
    chmod +x /usr/local/bin/cloudflared

# Non-root user (UID 1000 matches typical host user for volume permissions)
RUN groupadd -g 1000 unarr && useradd -u 1000 -g 1000 -m -d /home/unarr unarr

# Default directories
RUN mkdir -p /config /downloads /data && \
    chown -R unarr:unarr /config /downloads /data

# The container starts as root ONLY to chown the mounts, then execs as
# PUID:PGID (default 1000:1000 — identical to the old `USER unarr`). Baking the
# uid instead made every NAS fail: Synology uids start at 1024, unRAID uses
# 99:100, so the bind-mounted /config and /downloads were unwritable and the
# agent died on its first write. See docker-entrypoint.sh.
COPY docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh
RUN chmod +x /usr/local/bin/docker-entrypoint.sh

COPY --from=builder /unarr /usr/local/bin/unarr

# Environment: point config/data to container paths
ENV UNARR_CONFIG_DIR=/config
ENV UNARR_DOWNLOAD_DIR=/downloads
ENV XDG_DATA_HOME=/data

# Mark this as a container install so the agent reports isDocker=true to the web
# (which then shows a `docker pull` command instead of the in-app update button —
# the binary self-update refuses to run in Docker). Covers podman/containerd too,
# which don't create /.dockerenv. See internal/agent/RunningInDocker.
ENV UNARR_DOCKER=1

# NVIDIA passthrough defaults. `--gpus all` alone only grants the "utility" +
# "compute" capabilities; nvenc needs "video", and "graphics" makes the runtime
# mount the NVIDIA Vulkan ICD (nvidia_icd.json — the load-bearing piece — plus
# GLX/EGL libs) so ffmpeg's libplacebo filter (GPU HDR tonemap, paired with
# libvulkan1 above) can create a Vulkan device. "compute" alone does NOT mount
# the ICD. Baking these here means a plain `docker run --gpus all` (or the compose
# device reservation) lights up HW transcode + GPU tonemap with zero extra flags.
# Harmless when no GPU is attached.
ENV NVIDIA_VISIBLE_DEVICES=all
ENV NVIDIA_DRIVER_CAPABILITIES=video,compute,utility,graphics

# Select the iHD VAAPI driver for Intel QuickSync (installed on amd64 above).
# Without this libva may probe the wrong/absent driver on some hosts; iHD is the
# modern Intel driver (Gen9+). Harmless on NVIDIA/arm64 hosts where no libva
# device is used (QSV simply isn't selected). Pass /dev/dri via `--device`.
ENV LIBVA_DRIVER_NAME=iHD

VOLUME ["/config", "/downloads", "/data"]

# Without this, a container whose daemon has died reports "running" forever:
# the entrypoint process is still alive, and nothing else looks at whether the
# thing it supervises is. Docker, Portainer, Swarm, k8s and `compose --wait`
# all read this and nothing else.
#
# --quick is the load-bearing flag. It runs ONLY the local checks — config,
# download dir, disk, daemon liveness — and makes NO network call. A
# healthcheck that reaches the API marks the container unhealthy on every
# transient blip, Docker restarts it, and one ISP hiccup becomes a restart loop
# across the fleet. Warnings never produce a non-zero exit for the same reason:
# Docker's only response to "unhealthy" is to restart, and a missing par2 is
# not a reason to restart anything.
#
# start-period is generous because the first boot registers the agent and scans
# the library, and neither is instant on a NAS.
HEALTHCHECK --interval=60s --timeout=15s --start-period=90s --retries=3 \
  CMD unarr doctor --quick --json > /dev/null || exit 1

ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"]
# `up` is `start` plus one extra ability: if UNARR_AUTHKEY is set it redeems the
# key first. With CMD=start, `docker run -e UNARR_AUTHKEY=… unarr/cli` ignored the
# key and died with "no API key configured" — the container has no tty for the
# wizard, so that was the end of the road. `up` with no key and a stored
# credential behaves exactly like `start`.
CMD ["up"]