From c0730ecb8c9c8a4562af534ca717af72a06ddd61 Mon Sep 17 00:00:00 2001 From: Rebecca Sliter <571084+rsliter@users.noreply.github.com> Date: Thu, 22 Oct 2026 15:07:46 -0700 Subject: [PATCH] feat(docker): support explicit WSL all-GPU injection Keep NVIDIA injection under Docker driver ownership and validate the persisted selection during admission and restart. Closes #4849 Signed-off-by: Rebecca Sliter <571084+rsliter@users.noreply.github.com> --- crates/openshell-driver-docker/README.md | 16 +++ crates/openshell-driver-docker/src/lib.rs | 123 +++++++++++++---- crates/openshell-driver-docker/src/tests.rs | 125 ++++++++++++++++++ crates/openshell-driver-docker/src/wsl_gpu.rs | 112 ++++++++++++++++ docs/how-it-works/gateways/configuration.mdx | 24 ++++ docs/how-it-works/sandboxes/runtimes.mdx | 7 +- skills/openshell-cli/SKILL.md | 6 + 7 files changed, 387 insertions(+), 26 deletions(-) create mode 100644 crates/openshell-driver-docker/src/wsl_gpu.rs diff --git a/crates/openshell-driver-docker/README.md b/crates/openshell-driver-docker/README.md index 5044ec44c3..facf014b0c 100644 --- a/crates/openshell-driver-docker/README.md +++ b/crates/openshell-driver-docker/README.md @@ -159,6 +159,22 @@ retain legacy baseline handling on restart. Admission checks persisted context for existing containers and the creation context for new ones. Non-GPU sandboxes receive no CDI context or spec mounts. +## Docker Desktop WSL NVIDIA injection + +`driver_config.nvidia_wsl_gpus = "all"` explicitly selects NVIDIA injection +through the normal workload creation path. It requires a GPU resource request +without a count, and a Docker daemon reporting Docker Desktop and a WSL2 kernel. +It conflicts with CDI selection and does not change ordinary CDI requests. +The driver rechecks daemon eligibility during preflight and provisioning. + +The protected boundary config and runtime descriptor both record the all-GPU +selection. Admission compares the exact NVIDIA device request and immutable +container identity before restart/reconciliation, independently of resource +label enforcement. The generic GPU claim enables the existing workload-side +WSL device/library baseline. No supervisor credentials or CDI projections are +copied into a replacement container. Real WSL CUDA and lifecycle qualification +is required before treating this route as validated. + ## Driver Config Mounts The gateway forwards the `docker` block from `--driver-config-json`. Supported diff --git a/crates/openshell-driver-docker/src/lib.rs b/crates/openshell-driver-docker/src/lib.rs index 563f356b14..6f05a7b3c3 100644 --- a/crates/openshell-driver-docker/src/lib.rs +++ b/crates/openshell-driver-docker/src/lib.rs @@ -7,6 +7,7 @@ mod isolation; pub mod otel_tracing; +mod wsl_gpu; use bollard::Docker; use bollard::errors::Error as BollardError; @@ -359,6 +360,7 @@ struct DockerGpuRuntimeCapabilities { #[cfg(test)] cdi_spec_dirs: Vec, wsl_all_gpu_fallback_enabled: bool, + nvidia_wsl_supported: bool, } impl DockerGpuRuntimeCapabilities { @@ -1007,6 +1009,7 @@ struct DockerSandboxDriverConfig { deserialize_with = "deserialize_optional_non_empty_string_list" )] cdi_devices: Option>, + nvidia_wsl_gpus: Option, mounts: Vec, } @@ -1170,6 +1173,7 @@ impl DockerComputeDriver { #[cfg(test)] cdi_spec_dirs, wsl_all_gpu_fallback_enabled, + nvidia_wsl_supported: wsl_gpu::supported_daemon(&info), }; validate_docker_app_armor_profile(docker_config.app_armor_profile.as_ref(), &info)?; let gateway_port = gateway_bind_address.port(); @@ -1297,7 +1301,8 @@ impl DockerComputeDriver { limit_supported: true, }), gpu: Some(GpuResourceCapabilities { - default_selection_supported: self.config.gpu.cdi_supported, + default_selection_supported: self.config.gpu.cdi_supported + || self.config.gpu.nvidia_wsl_supported, count_selection_supported: self.config.gpu.cdi_supported, }), }), @@ -1354,7 +1359,15 @@ impl DockerComputeDriver { } } let gpu_requirements = driver_gpu_requirements(spec.resource_requirements.as_ref()); - Self::validate_gpu_request(gpu_requirements, config.gpu.cdi_supported, &driver_config)?; + if driver_config.nvidia_wsl_gpus.is_some() { + wsl_gpu::validate_request( + gpu_requirements, + &driver_config, + config.gpu.nvidia_wsl_supported, + )?; + } else { + Self::validate_gpu_request(gpu_requirements, config.gpu.cdi_supported, &driver_config)?; + } Ok(ValidatedDockerSandbox { template, driver_config, @@ -1486,13 +1499,6 @@ impl DockerComputeDriver { &self, container: &bollard::models::ContainerInspectResponse, ) -> Result<(), Status> { - // Only CDI containers carry a projection context, so other containers - // are admitted without depending on the bootstrap file. - if !self.config.resource_admission.enabled || !has_cdi_device_request(container) { - return self - .admit_container_resources_with_cdi_context(container, None) - .await; - } let sandbox_id = container .config .as_ref() @@ -1501,8 +1507,28 @@ impl DockerComputeDriver { .ok_or_else(|| Status::failed_precondition("sandbox lacks identity"))?; let path = docker_boundary_state_dir_by_id(sandbox_id, &self.config)?.join(BOUNDARY_CONFIG_FILE); - let cdi_context = read_docker_cdi_context(&path).await?; - self.admit_container_resources_with_cdi_context(container, cdi_context.as_ref()) + let has_gpu_request = container + .host_config + .as_ref() + .and_then(|host| host.device_requests.as_ref()) + .is_some_and(|requests| !requests.is_empty()); + // Preserve admission of older CPU fixtures without a bootstrap. For a + // provisioned workload, always check the protected selection, even if + // its Docker device request was removed. + if !has_gpu_request + && !tokio::fs::try_exists(&path) + .await + .map_err(|error| Status::unavailable(error.to_string()))? + { + return self + .admit_container_resources_with_cdi_context(container, None) + .await; + } + // Read protected selection even when the inspected device request was removed. + // Provisioning uses the explicit creation context before this file exists. + let boundary = read_docker_boundary_config(&path).await?; + wsl_gpu::admit_persisted(container, &boundary)?; + self.admit_container_resources_with_cdi_context(container, boundary.cdi_context.as_ref()) .await } @@ -1684,6 +1710,19 @@ impl DockerComputeDriver { u32, ) -> Result, CdiGpuSelectionError>, ) -> Result>, Status> { + if driver_config.nvidia_wsl_gpus.is_some() { + let info = self + .docker + .info() + .await + .map_err(|err| internal_status("query Docker daemon info", err))?; + wsl_gpu::validate_request( + gpu_requirements, + driver_config, + wsl_gpu::supported_daemon(&info), + )?; + return Ok(None); + } if let Some(cdi_devices) = driver_config.cdi_devices.as_deref() { validate_cdi_gpu_device_request( gpu_requirements, @@ -2164,7 +2203,12 @@ impl DockerComputeDriver { )); } }; - let outer_fence_error = match validate_docker_outer_fence(&inspected) { + let outer_fence_error = match validate_docker_outer_fence(&inspected).and_then(|()| { + wsl_gpu::admit_request( + &inspected, + validated.driver_config.nvidia_wsl_gpus.is_some(), + ) + }) { Ok(()) => self .admit_container_resources_with_cdi_context(&inspected, cdi_context.as_ref()) .await @@ -4716,7 +4760,7 @@ fn validate_cdi_spec_mount_config( /// A missing or undecodable bootstrap is persistent and fails admission. /// Other read errors may be transient and defer admission to the next pass. -async fn read_docker_cdi_context(path: &Path) -> Result, Status> { +async fn read_docker_boundary_config(path: &Path) -> Result { let contents = tokio::fs::read(path).await.map_err(|error| { let message = format!("read Docker sandbox bootstrap for admission: {error}"); if error.kind() == std::io::ErrorKind::NotFound { @@ -4730,7 +4774,12 @@ async fn read_docker_cdi_context(path: &Path) -> Result, Stat "decode Docker sandbox bootstrap for admission: {error}" )) })?; - Ok(config.cdi_context) + Ok(config) +} + +#[cfg(test)] +async fn read_docker_cdi_context(path: &Path) -> Result, Status> { + Ok(read_docker_boundary_config(path).await?.cdi_context) } /// Replace the file atomically so concurrent readers never see a partial write. @@ -5049,6 +5098,10 @@ async fn prepare_docker_boundary_files( workload_identity: &ResolvedWorkloadIdentity, cdi_context: Option<&CdiContext>, ) -> Result<(), Status> { + let nvidia_wsl_all = DockerComputeDriver::validated_sandbox(sandbox, config)? + .driver_config + .nvidia_wsl_gpus + .is_some(); let directory = docker_boundary_state_dir(sandbox, config)?; let workspace_root = driver_mounts::resolve_oci_workspace_root(&image.working_dir) .map_err(Status::failed_precondition)?; @@ -5064,7 +5117,7 @@ async fn prepare_docker_boundary_files( let tls = generate_sandbox_tls_material(session_id) .map_err(|error| Status::internal(format!("generate Docker boundary TLS: {error}")))?; let verification_keys = gateway_verification_keys(&launch_authentication.verification_keys)?; - let provisioning = isolation::DockerBoundarySpec { + let mut provisioning = isolation::DockerBoundarySpec { boundary_id: sandbox.id.clone(), generation: launch_authentication .supervisor @@ -5077,7 +5130,7 @@ async fn prepare_docker_boundary_files( verification_keys, container_id: container_id.to_string(), image_identity: image.id.clone(), - gpu_requested: cdi_context.is_some(), + gpu_requested: cdi_context.is_some() || nvidia_wsl_all, cdi_context: cdi_context.cloned(), listener_socket: PathBuf::from(BOUNDARY_SOCKET_MOUNT_PATH), control_socket: PathBuf::from(BOUNDARY_SOCKET_MOUNT_PATH), @@ -5099,6 +5152,16 @@ async fn prepare_docker_boundary_files( } .provision() .map_err(|error| Status::failed_precondition(error.to_string()))?; + if nvidia_wsl_all { + provisioning + .boundary_config + .resource_claims + .insert(wsl_gpu::SELECTION_CLAIM.into(), "all".into()); + provisioning + .runtime_descriptor + .resource_claims + .insert(wsl_gpu::SELECTION_CLAIM.into(), "all".into()); + } let boundary_config = provisioning .boundary_config .encode() @@ -6367,13 +6430,27 @@ fn build_container_create_body_for_image( ..Default::default() }); let user_bind_strings = docker_driver_bind_strings(driver_config)?; - let device_requests = gpu_device_ids.map(|device_ids| { - vec![DeviceRequest { - driver: Some("cdi".to_string()), - device_ids: Some(device_ids.to_vec()), - ..Default::default() - }] - }); + let device_requests = if driver_config.nvidia_wsl_gpus.is_some() { + wsl_gpu::validate_request( + driver_gpu_requirements(spec.resource_requirements.as_ref()), + driver_config, + config.gpu.nvidia_wsl_supported, + )?; + if gpu_device_ids.is_some() { + return Err(Status::invalid_argument( + "WSL NVIDIA injection cannot include CDI device IDs", + )); + } + Some(vec![wsl_gpu::device_request()]) + } else { + gpu_device_ids.map(|device_ids| { + vec![DeviceRequest { + driver: Some("cdi".to_string()), + device_ids: Some(device_ids.to_vec()), + ..Default::default() + }] + }) + }; let mut labels = template.labels.clone(); labels.insert( "openshell.ai/private-image-volume-targets".into(), diff --git a/crates/openshell-driver-docker/src/tests.rs b/crates/openshell-driver-docker/src/tests.rs index 4411179101..4fdadb4be0 100644 --- a/crates/openshell-driver-docker/src/tests.rs +++ b/crates/openshell-driver-docker/src/tests.rs @@ -614,6 +614,7 @@ fn runtime_config() -> DockerDriverRuntimeConfig { cdi_supported: false, cdi_spec_dirs: Vec::new(), wsl_all_gpu_fallback_enabled: false, + nvidia_wsl_supported: false, }, sandbox_pids_limit: openshell_core::config::default_sandbox_pids_limit(), enable_bind_mounts: false, @@ -4958,3 +4959,127 @@ fn normalize_pull_reference_appends_latest_only_when_untagged() { "registry:5000/team/app:v1" ); } + +#[test] +fn wsl_gpu_requires_desktop_daemon_and_wsl_kernel() { + for (os, kernel, expected) in [ + ("Docker Desktop", "6.6.87.2-microsoft-standard-WSL2", true), + ("Docker Desktop", "6.12-linuxkit", false), + ("Ubuntu 24.04", "6.6-microsoft-standard-WSL2", false), + ("Docker Desktop", "", false), + ] { + let info = SystemInfo { + operating_system: Some(os.into()), + kernel_version: Some(kernel.into()), + ..Default::default() + }; + assert_eq!(wsl_gpu::supported_daemon(&info), expected, "{os} {kernel}"); + } +} + +#[test] +fn wsl_gpu_rejects_implicit_or_conflicting_selection() { + let mut driver_config: DockerSandboxDriverConfig = + serde_json::from_value(serde_json::json!({"nvidia_wsl_gpus":"all"})).unwrap(); + let default_gpu = GpuResourceRequirements { count: None }; + assert!(wsl_gpu::validate_request(Some(&default_gpu), &driver_config, true).is_ok()); + assert!(wsl_gpu::validate_request(Some(&default_gpu), &driver_config, false).is_err()); + assert!(wsl_gpu::validate_request(None, &driver_config, true).is_err()); + for count in [0, 1, 2] { + assert!( + wsl_gpu::validate_request( + Some(&GpuResourceRequirements { count: Some(count) }), + &driver_config, + true + ) + .is_err() + ); + } + driver_config.cdi_devices = Some(vec!["nvidia.com/gpu=all".into()]); + assert!(wsl_gpu::validate_request(Some(&default_gpu), &driver_config, true).is_err()); + for value in [ + serde_json::json!(true), + serde_json::json!("0"), + serde_json::json!("auto"), + ] { + assert!( + serde_json::from_value::( + serde_json::json!({"nvidia_wsl_gpus":value}) + ) + .is_err() + ); + } +} + +#[test] +fn wsl_gpu_creation_preserves_enforcement_and_requires_operator_opt_in() { + let mut sandbox = test_sandbox(); + let spec = sandbox.spec.as_mut().unwrap(); + spec.resource_requirements = Some(gpu_resources(None)); + spec.template.as_mut().unwrap().driver_config = Some(prost_types::Struct { + fields: std::iter::once(( + "nvidia_wsl_gpus".into(), + prost_types::Value { + kind: Some(prost_types::value::Kind::StringValue("all".into())), + }, + )) + .collect(), + }); + let mut config = runtime_config(); + config.gpu.nvidia_wsl_supported = true; + config.resource_admission.enabled = true; + let validated = DockerComputeDriver::validated_sandbox(&sandbox, &config).unwrap(); + let body = build_container_create_body_with_gpu_devices( + &sandbox, + &config, + &validated.driver_config, + None, + ) + .unwrap(); + let host = body.host_config.unwrap(); + assert_eq!(host.device_requests, Some(vec![wsl_gpu::device_request()])); + assert_eq!(host.network_mode.as_deref(), Some("none")); + assert_eq!(host.cap_drop, Some(vec!["ALL".into()])); + assert!(host.cap_add.as_ref().is_none_or(Vec::is_empty)); + assert!( + host.security_opt + .unwrap() + .iter() + .any(|option| option.contains("no-new-privileges")) + ); + assert!(!host.privileged.unwrap_or(false)); + config.allow_driver_config = false; + assert!(DockerComputeDriver::validated_sandbox(&sandbox, &config).is_err()); +} + +#[test] +fn wsl_gpu_restart_rejects_selection_and_identity_tampering() { + let mut boundary = admission_boundary_config(None); + boundary + .resource_claims + .insert(wsl_gpu::SELECTION_CLAIM.into(), "all".into()); + let valid = serde_json::json!({"Id":"container-test", "HostConfig":{"DeviceRequests":[{"Driver":"nvidia","Count":-1,"Capabilities":[["gpu"]]}]}}); + wsl_gpu::admit_persisted(&serde_json::from_value(valid.clone()).unwrap(), &boundary).unwrap(); + for (pointer, replacement) in [ + ("/Id", serde_json::json!("foreign")), + ("/HostConfig/DeviceRequests", serde_json::json!([])), + ( + "/HostConfig/DeviceRequests/0/Driver", + serde_json::json!("cdi"), + ), + ("/HostConfig/DeviceRequests/0/Count", serde_json::json!(1)), + ( + "/HostConfig/DeviceRequests/0/Capabilities", + serde_json::json!([["compute"]]), + ), + ] { + let mut changed = valid.clone(); + *changed.pointer_mut(pointer).unwrap() = replacement; + assert!( + wsl_gpu::admit_persisted(&serde_json::from_value(changed).unwrap(), &boundary).is_err(), + "{pointer}" + ); + } + boundary.resource_claims.remove(wsl_gpu::SELECTION_CLAIM); + assert!(wsl_gpu::admit_persisted(&serde_json::from_value(valid).unwrap(), &boundary).is_err()); +} diff --git a/crates/openshell-driver-docker/src/wsl_gpu.rs b/crates/openshell-driver-docker/src/wsl_gpu.rs new file mode 100644 index 0000000000..eb8ea64ffb --- /dev/null +++ b/crates/openshell-driver-docker/src/wsl_gpu.rs @@ -0,0 +1,112 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +//! Explicit Docker Desktop WSL NVIDIA injection. Never infer all-GPU consent +//! from the ordinary one-GPU resource request or a failed CDI selection. +use super::{BoundaryConfig, DockerSandboxDriverConfig, GpuResourceRequirements}; +use bollard::models::{ContainerInspectResponse, DeviceRequest, SystemInfo}; +use tonic::Status; + +pub const SELECTION_CLAIM: &str = "docker.nvidia_wsl_gpus"; + +#[derive(Debug, Clone, Copy, serde::Deserialize)] +pub enum AllGpus { + #[serde(rename = "all")] + All, +} + +pub fn supported_daemon(info: &SystemInfo) -> bool { + info.operating_system + .as_deref() + .is_some_and(|os| os.eq_ignore_ascii_case("Docker Desktop")) + && super::docker_info_reports_wsl2(info) +} + +pub fn validate_request( + gpu: Option<&GpuResourceRequirements>, + config: &DockerSandboxDriverConfig, + supported: bool, +) -> Result<(), Status> { + if !supported { + return Err(Status::failed_precondition( + "nvidia_wsl_gpus requires a Docker Desktop WSL2 daemon", + )); + } + if config.cdi_devices.is_some() || gpu.is_none() || gpu.is_some_and(|gpu| gpu.count.is_some()) { + return Err(Status::invalid_argument( + "nvidia_wsl_gpus=all requires a GPU request without count or cdi_devices; it explicitly grants all GPUs", + )); + } + Ok(()) +} + +pub fn device_request() -> DeviceRequest { + DeviceRequest { + driver: Some("nvidia".into()), + count: Some(-1), + capabilities: Some(vec![vec!["gpu".into()]]), + ..Default::default() + } +} + +pub fn admit_request(container: &ContainerInspectResponse, expected: bool) -> Result<(), Status> { + let requests = container + .host_config + .as_ref() + .and_then(|host| host.device_requests.as_deref()) + .unwrap_or_default(); + if expected { + let valid = requests.len() == 1 + && requests[0].driver.as_deref() == Some("nvidia") + && requests[0].count == Some(-1) + && requests[0].device_ids.as_ref().is_none_or(Vec::is_empty) + && requests[0] + .options + .as_ref() + .is_none_or(std::collections::HashMap::is_empty) + && requests[0].capabilities.as_ref() == device_request().capabilities.as_ref(); + if !valid { + return Err(Status::failed_precondition( + "Docker NVIDIA request differs from protected all-GPU selection", + )); + } + } else if requests + .iter() + .any(|request| request.driver.as_deref() != Some("cdi")) + { + return Err(Status::failed_precondition( + "unrecorded non-CDI Docker GPU request", + )); + } + Ok(()) +} + +pub fn admit_persisted( + container: &ContainerInspectResponse, + boundary: &BoundaryConfig, +) -> Result<(), Status> { + let selection = boundary.resource_claims.get(SELECTION_CLAIM); + if selection.is_some_and(|value| value != "all") { + return Err(Status::failed_precondition( + "unsupported protected WSL GPU selection", + )); + } + if selection.is_some() + && (boundary.cdi_context.is_some() + || boundary + .resource_claims + .get("docker.container_id") + .map(String::as_str) + != container.id.as_deref() + || boundary + .resource_claims + .get(openshell_sandbox_backend::GPU_RESOURCE_CLAIM) + .map(String::as_str) + != Some("true")) + { + return Err(Status::failed_precondition( + "WSL GPU selection is not bound to this GPU workload", + )); + } + admit_request(container, selection.is_some()) +} diff --git a/docs/how-it-works/gateways/configuration.mdx b/docs/how-it-works/gateways/configuration.mdx index 1954b957a7..da3065aa65 100644 --- a/docs/how-it-works/gateways/configuration.mdx +++ b/docs/how-it-works/gateways/configuration.mdx @@ -1135,6 +1135,30 @@ companion supervisor. The bundle extends trust for the TLS connection to an and the combined trust bundle exposed to workload processes. The gateway-host path is never mounted into or exposed to the workload container. +#### Docker Desktop WSL GPU injection + +On a Docker Desktop WSL2 daemon, an operator can opt into NVIDIA runtime +injection when a usable CDI route is unavailable. Enable `allow_driver_config` +for the Docker driver and keep resource admission enabled. Request all GPUs +explicitly: + +```shell +openshell sandbox create --gpu \ + --driver-config-json '{"docker":{"nvidia_wsl_gpus":"all"}}' \ + --from -- +``` + +This mode grants every GPU exposed by the Docker daemon. It rejects an explicit +GPU count or `cdi_devices` selection, and never runs automatically after a CDI +failure. Ordinary GPU requests continue to use CDI. Native Linux Docker inside +WSL, macOS Docker Desktop, and Jetson are outside this mode. + +OpenShell creates the workload with NVIDIA injection, retains its isolated +network and non-root identity, and checks the recorded GPU selection before +restart. Docker Desktop must have working NVIDIA GPU support and host networking +enabled for the supervisor. Additional host bind mounts and disabled resource +admission are not required. + ### Podman Each Podman sandbox uses two containers. The workload container runs `openshell-sandbox` with `network=none`; the supervisor container runs on the host network and initiates policy-approved upstream connections. A private volume carries their authenticated Unix-domain socket. Configure the supervisor gateway CA once under `[openshell.gateway]`; the gateway validates and injects it into the selected local driver. The supervisor authenticates RPCs with a sandbox bearer token. diff --git a/docs/how-it-works/sandboxes/runtimes.mdx b/docs/how-it-works/sandboxes/runtimes.mdx index 3e9058255b..dcdd92008b 100644 --- a/docs/how-it-works/sandboxes/runtimes.mdx +++ b/docs/how-it-works/sandboxes/runtimes.mdx @@ -77,11 +77,12 @@ The gateway does not start or supervise extension drivers. Restrict the socket s ## Size Sandboxes -Use `--cpu`, `--memory`, and `--gpu` on `openshell sandbox create` to request resources. `--gpu` alone requests one GPU; `--gpu COUNT` requests more. +Use `--cpu`, `--memory`, and `--gpu` on `openshell sandbox create` to request resources. `--gpu` alone uses the driver's default selection, normally one GPU; `--gpu COUNT` requests a specific count. The explicit Docker Desktop WSL all-GPU mode described below overrides the default selection and rejects a count. | Driver | CPU and memory | GPU | |---|---|---| -| Docker, Podman | Runtime limits | NVIDIA CDI devices | +| Docker | Runtime limits | NVIDIA CDI devices, or explicit all-GPU injection on Docker Desktop WSL | +| Podman | Runtime limits | NVIDIA CDI devices | | Kubernetes | Container requests and limits | `nvidia.com/gpu` resource limit | | MicroVM | Ignored; use `vcpus` and `mem_mib` | One GPU through `gpu_device_ids` | @@ -119,7 +120,7 @@ socket_path = "/var/run/docker.sock" Common options in `[openshell.drivers.docker]` are `socket_path`, `grpc_endpoint`, `sandbox_runtime_image`, `supervisor_image`, `image_pull_policy`, and `sandbox_pids_limit`. When `socket_path` is unset, the driver uses the socket found by auto-detection. -Docker Desktop must have host networking enabled, and it cannot use Enhanced Container Isolation. Set `grpc_endpoint` when sandboxes cannot reach the gateway on host loopback. For GPU sandboxes, configure Docker CDI before starting the gateway. +Docker Desktop must have host networking enabled, and it cannot use Enhanced Container Isolation. Set `grpc_endpoint` when sandboxes cannot reach the gateway on host loopback. For ordinary GPU requests, configure Docker CDI before starting the gateway. On Docker Desktop WSL with working NVIDIA GPU support, an operator can instead enable [explicit all-GPU injection](/how-it-works/gateways/configuration#docker-desktop-wsl-gpu-injection). This mode preserves OpenShell's workload isolation and lifecycle ownership; it does not run automatically after CDI failure. For Docker GPU/CDI sandboxes, OpenShell uses Docker's selected CDI device IDs and daemon-reported CDI spec directories to build a workload CDI context. The diff --git a/skills/openshell-cli/SKILL.md b/skills/openshell-cli/SKILL.md index 75d2de5057..b555f6b20b 100644 --- a/skills/openshell-cli/SKILL.md +++ b/skills/openshell-cli/SKILL.md @@ -354,6 +354,12 @@ config opt-in. Consult the published gateway configuration reference before changing admission settings; do not recommend disabling admission to bypass a denial. Put driver config on a template only when it should be reused. +Docker Desktop WSL builds exposing `docker.nvidia_wsl_gpus` can explicitly +request `"all"` with an uncounted `--gpu` request. This requires operator-enabled +driver config and grants all daemon GPUs; never substitute it for a requested +count or CDI ID, or enable it automatically after a failed CDI request. Consult +the installed version's gateway configuration documentation before use. + ### Manage sandbox workload templates ```bash