← All projects

Systems debugging · Docker · Root-cause writeup

Plex AMD VAAPI — the musl fix

Getting AMD iGPU/GPU hardware transcoding working in a Dockerized Plex Media Server — and a full write-up of why every "obvious" fix fails. A case study in reading symptoms down to the ABI.

musl vs glibc ABI dlopen / ELF patchelf RPATH VAAPI / radeonsi multi-stage Docker

The symptom

Install mesa-va-drivers into a Plex container, pass /dev/dri, and… you still get software transcoding — with cryptic loader errors:

Error relocating .../radeonsi_drv_video.so: __isoc23_strtoul: symbol not found
Error relocating .../radeonsi_drv_video.so: fcntl64: symbol not found
Error relocating .../radeonsi_drv_video.so: qsort_r: symbol not found

Every attempted fix hits a different missing symbol. That pattern is the clue.

The root cause

Modern Plex's Plex Transcoder is a musl binary — it bundles its own ld-musl (musl 1.2.2) and its own musl libva, but ships no VA driver, expecting the image to provide radeonsi_drv_video.so. Every mesa-va-drivers package from Ubuntu/Debian is built against glibc. You cannot dlopen a glibc shared object into a musl process — it's a different libc ABI, not a version mismatch. Hence the parade of missing glibc symbols.

The decisive evidence that Plex is musl, not glibc:
$ docker exec plex /usr/lib/plexmediaserver/lib/libc.so 2>&1 | head -3
musl libc (x86_64)
Version 1.2.2
Dynamic Program Loader

The debugging journey

The writeup keeps the full trail so nobody has to repeat it — each error precisely fingerprints a libc and version:

AttemptErrorWhat it proved
Ubuntu mesa-va-drivers__isoc23_strtoulsymbol is glibc 2.38+
Ubuntu 22.04 (glibc 2.35) mesafcntl64symbol is glibc 2.28+ — Plex's libc is older still
Inspect Plex's libcfound ld-muslPlex Transcoder is musl, not glibc at all
Alpine 3.20 (musl 1.2.5) mesaqsort_rsymbol is musl 1.2.3+; Plex bundles 1.2.2
Alpine 3.15 (musl 1.2.2) mesaVAAPI version 1.22musl version matches — hardware transcode

The fix

Supply a musl-built radeonsi driver — exactly what Alpine ships as mesa-va-gallium. Match the Alpine release to Plex's bundled musl version (1.2.2 → Alpine 3.15; qsort_r landed in 1.2.3, so 3.16+ is too new), bundle the driver and its musl deps via a multi-stage build, patchelf the RPATH so the dlopen'd driver resolves its siblings, and point Plex at it:

FROM alpine:3.15 AS vaapi
RUN apk add --no-cache mesa-va-gallium libva patchelf
# gather radeonsi_drv_video.so + musl deps into /opt/vaapi, patchelf RPATH

FROM lscr.io/linuxserver/plex:latest
COPY --from=vaapi /opt/vaapi /opt/vaapi
ENV LIBVA_DRIVERS_PATH=/opt/vaapi/dri LIBVA_DRIVER_NAME=radeonsi

The fiddly bits the writeup calls out

Why it's here: this is the kind of problem most people give up on and leave at "software transcode." Chasing a vague "no hardware acceleration" symptom down through the dynamic loader to a libc-ABI mismatch — and turning it into a reproducible Dockerfile plus a writeup others can use — is exactly the systems-debugging work I enjoy.
← Back to all projects