Systems debugging · Docker · Root-cause writeup
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.
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.
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.
$ 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 writeup keeps the full trail so nobody has to repeat it — each error precisely fingerprints a libc and version:
| Attempt | Error | What it proved |
|---|---|---|
| Ubuntu mesa-va-drivers | __isoc23_strtoul | symbol is glibc 2.38+ |
| Ubuntu 22.04 (glibc 2.35) mesa | fcntl64 | symbol is glibc 2.28+ — Plex's libc is older still |
| Inspect Plex's libc | found ld-musl | Plex Transcoder is musl, not glibc at all |
| Alpine 3.20 (musl 1.2.5) mesa | qsort_r | symbol is musl 1.2.3+; Plex bundles 1.2.2 |
| Alpine 3.15 (musl 1.2.2) mesa | ✅ VAAPI version 1.22 | musl version matches — hardware transcode |
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
qsort_r ⇒ musl 1.2.3, __isoc23_* ⇒ glibc 2.38, fcntl64 ⇒ glibc 2.28. Read the error, identify the libc.LIBVA_DRIVERS_PATH and named via LIBVA_DRIVER_NAME.patchelf --set-rpath on the driver and its deps makes dlopen resolve siblings despite Plex's locked LD_LIBRARY_PATH.LIBVA_* vars in the container environment: so with-contenv exports them into PMS and the transcoder children it spawns — a Dockerfile ENV alone proved unreliable.