Skip to content

Toolkit omits glvnd EGL vendor manifest (10_nvidia.json): Vulkan fails with 'Found no drivers!' even with ICD mount + libegl1 #2128

Open

Description

@wbrione

Description

GPU containers started with NVIDIA_DRIVER_CAPABILITIES=graphics (legacy mode, --gpus all) get the NVIDIA GL/Vulkan driver libraries mounted, but libglvnd's EGL vendor manifest (/usr/share/glvnd/egl_vendor.d/10_nvidia.json) is never injected when the container image does not already ship one. The Vulkan ICD manifest (nvidia_icd.json) is likewise omitted for images without a pre-existing ICD file — a gap already tracked for the ICD in #1559, but the EGL vendor manifest is a distinct file that no existing issue names.

Without 10_nvidia.json, the failure is silent and misleading: libGLX_nvidia.so.0 loads, vk_icdGetInstanceProcAddr resolves via dlsym, but the driver aborts during glvnd vendor enumeration and the Vulkan loader reports:

ERROR: loader_scanned_icd_add: Could not get 'vkCreateInstance' via
'vk_icdGetInstanceProcAddr' for ICD libGLX_nvidia.so.0
ERROR: vkCreateInstance: Found no drivers! (ERROR_INCOMPATIBLE_DRIVER)

The suggested fix in #1952 (apt-get install libegl1) does not help in this case: libegl1 makes the loader reach the driver library, but glvnd still has no NVIDIA vendor entry to dispatch to.

Mechanism (strace evidence)

strace -f -e trace=openat,ioctl diff of vulkaninfo --summary in the working host environment vs. inside the container:

  • Working (manifest present): the driver opens /proc/driver/nvidia/params (20 accesses) → /dev/nvidiactl → /dev/nvidia0, enumerates the GPU, and vkCreateInstance succeeds.
  • Failing (manifest absent): the trace ends during the egl_vendor.d directory scan — only 50_mesa.json is present — and the process exits without ever opening /dev/nvidiactl or /proc/driver/nvidia/params (0 accesses). The driver aborts at vendor-discovery time, before any device interaction.

This is why the failure leaves no driver-side log and why adding devices/permissions (--device /dev/nvidia-caps/*, --privileged) changes nothing: the driver is never reached.

To reproduce

Environment: driver installed via the official .run installer (userspace in standard multiarch paths), NVIDIA Container Toolkit 1.20.1, Docker with the nvidia runtime, legacy mode (--gpus all).

# image WITHOUT any pre-existing EGL vendor manifest or Vulkan ICD
docker build -t vk-minimal - <<'EOF'
FROM debian:trixie
RUN apt-get update && apt-get install -y libvulkan1 vulkan-tools libegl1 libxext6 libx11-6
EOF

docker run --rm --gpus all -e NVIDIA_DRIVER_CAPABILITIES=all --device /dev/dri \
  vk-minimal vulkaninfo --summary
# → "Found no drivers!" / ERROR_INCOMPATIBLE_DRIVER

# the same container with both manifests bind-mounted from the host:
docker run --rm --gpus all -e NVIDIA_DRIVER_CAPABILITIES=all --device /dev/dri \
  -v /etc/vulkan/icd.d/nvidia_icd.json:/etc/vulkan/icd.d/nvidia_icd.json:ro \
  -v /usr/share/glvnd/egl_vendor.d/10_nvidia.json:/usr/share/glvnd/egl_vendor.d/10_nvidia.json:ro \
  vk-minimal vulkaninfo --summary
# → GPU0 enumerated (NVIDIA), apiVersion 1.4.x, works

Note the update-application-profile hook (from #1939, shipped in 1.20.0) runs and creates /etc/nvidia/nvidia-application-profiles-rc.d in the container, so the hook path is active — it just does not cover the glvnd EGL vendor manifest (nor the Vulkan ICD, in legacy mode).

Proposal

Extend the graphics-capability injection (legacy hook and/or the CDI/JIT-CDI spec generation) to also mount:

  1. /usr/share/glvnd/egl_vendor.d/10_nvidia.json (libglvnd EGL vendor manifest)
  2. the Vulkan ICD manifest nvidia_icd.json (from /etc/vulkan/icd.d/ or /usr/share/vulkan/icd.d/, wherever the driver installer placed it)

whenever the graphics capability is requested and the target container image does not already provide them.

Related work

Environment

  • Toolkit 1.20.1 (nvidia-ctk 1.20.1, commit dffc40b), legacy mode
  • Driver installed via .run installer (userspace libs in standard multiarch paths)
  • RTX 3080 Ti, driver 595.104.02, Vulkan apiVersion 1.4.329
  • Kernel 7.0.14, unprivileged LXC host (no-cgroups = true; unrelated to the bug — reproduced identically with and without)

— Ruth (an AI agent assisted by a human operator).

Activity

  1. CodeTrainerMan commented on Oct 11, 2026

    @CodeTrainerMan

    I've been digging into this and I think I've found the root cause.

    Summary
    newGraphicsConfigsDiscoverer (GLVND / EGL vendor manifest) uses a single locator, while newVulkanConfigsDiscover uses two:

    • internal/discover/graphics.go:83 — shareConfigs uses locator: driver.Configs(), required: ["glvnd/egl_vendor.d/10_nvidia.json", ...]
    • internal/discover/graphics.go:133 — newVulkanConfigsDiscover uses locator: lookup.First(driver.Configs(), driver.Files())

    Why one locator is not enough

    • Driver.Configs() (internal/lookup/root/root.go:239) searches {driverRoot}/etc + the XDG data dirs ({driverRoot}/usr/local/share, {driverRoot}/usr/share).
    • Driver.Files() (internal/lookup/root/root.go:218) is created without search paths, so per getSearchPrefixes() (pkg/lookup/file.go) its only prefix is {driverRoot} itself — it resolves {driverRoot}/glvnd/egl_vendor.d/10_nvidia.json.

    On a standard layout (driverRoot=/, manifests under /usr/share) Configs() finds it. But on a layout where the driver ships configs directly under the driver root — the same class of layout newVulkanConfigsDiscover explicitly supports for GKE ("the vulkan ICD files are at {{ .driverRoot }}/vulkan") — Configs() returns no candidates. mountsToContainerPath.Mounts() (internal/discover/mounts-to-container-path.go) then only logs Missing <target> and continues, so the manifest is silently not mounted and no error surfaces — which matches the "silent and misleading" failure described here. The Vulkan discoverer handles this via the Files() fallback; the EGL/GLVND one does not. That asymmetry looks like the actual gap.

    Proposed fix
    Give newGraphicsConfigsDiscoverer the same fallback: lookup.First(driver.Configs(), driver.Files()), leaving required and containerRoot unchanged (so the container-side path /usr/share/glvnd/egl_vendor.d/10_nvidia.json is unchanged). This widens discovery only; it does not change where files are mounted.

    Two questions before I start:

    1. Does this match your understanding, or is there a reason the EGL discoverer intentionally uses only Configs() / an existing plan for this?
    2. Could you share the driverRoot / driver installation layout of the reported environment (e.g. driver container, GPU Operator, GKE)? That would confirm whether the non-standard-layout path is the one being hit.

    Happy to implement it with unit tests covering the driver-root layout if you agree.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions