4565 字
23 分钟
WSL2内部运行Docker并配置NVIDIA GPU
2026-07-09
2026-07-12

前言#

这篇文章主要记录在受限环境下通过WSL2运行Ubuntu-24.04-LTS,并在其中部署带NVIDIA GPU环境的Docker容器,这里所说的受限环境是指在高信息安全等级的设备上Docker Desktop被列为非标不合规软件,并且登陆账户权限受限,无管理员权限、无微软商店、无Winget包管理器且网络防火墙对相当一部分访问进行拦截的情况;

在常规个人设备或非严格受限设备下还是尽量选择Docker Desktop的方式,将后端直接部署在Windows中并设置允许WSL2发行版镜像调用,这套组合能避免很多折腾,非常丝滑,但这里并非讨论的主题,不做展开;

前置准备#

首先一切的大前提是你的操作系统支持WSL2的运行,可以参考这里

只要不是特别老旧的Win10、Win11版本,应该都满足需求;
(不会有涉及到这类生产环境的设备还停留在Win7之类的吧)

开启WSL和虚拟机功能支持#

这块在权限受限的情况下没法自己解决了,呼叫IT支持…

以管理员权限执行下列两条令开启对应虚拟机平台和WSL功能支持:

Powershell
dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart
dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart

或者手动去程序和功能勾选启用对应功能即可:

程序和功能配置

不管使用哪种方式,启用功能后需要重启一次设备;

下载WSL2内核离线更新包#

常规环境下WSL2内核是通过微软商店渠道更新的,显然在当前的环境下不适用,需要手动下载内核离线更新包:

下载完成后手动安装更新内核;

下载发行版离线安装包#

由于防火墙设置以及微软账号、商店等功能被砍了,无法通过正常的wsl --install -d <发行版名称>的方式在线下载镜像,必须手动下载完整离线安装包,这里需要借助一个第三方镜像站:

使用方式是将微软商店对应的软件包链接填到这个镜像站内解析即可,微软官方的WSL发行版列表如下:

在这里可以找到微软商店中提供的发行版列表,点击即可跳转到微软商店,只需要将这个连接复制下来扔到镜像站进行解析,下载对应发行版即可,例如对于Ubuntu 24.04.1 LTS解析结果如下:

Ubuntu离线安装包解析

下载第二条尾缀为.Appx.AppxBundle的安装包即可,这里直接给出直链:

离线发行版安装#

下载完离线发行版软件包后,使用管理员权限在终端中通过Add-AppxPackage安装下载的包:

Powershell
Add-AppxPackage <包文件>

安装离线发行版

安装完成后,应该会出现在应用菜单,单击运行即可在WSL中部署发行版: Ubuntu应用 发行版安装

等到提示Enter new UNIX username时,可以输入一个符合命名规范的普通Unix用户名,但是名称限制有点多,而且不能用--allow-bad-names强制启用不建议的名称,建立普通用户后需要设置sudu密码;

由于这里只有我使用这台设备,外边还有Windows密码拦着,并且我需要对WSL发行版的最高控制权 (主要是懒得输sudo密码),所以这里直接关闭这个窗口,随后在终端中输入WSL即可以root账户登入;

更新基础软件包#

执行下列指令更新基础软件包:

Terminal window
apt update
apt upgrade

移除无用软件包:

Terminal window
apt autoremove

一切完成后重新执行apt update,检查是否全部更新完毕:

Terminal window
root@HZW001-GF3TFYJ6:~# apt update
Hit:1 http://security.ubuntu.com/ubuntu noble-security InRelease
Hit:2 http://archive.ubuntu.com/ubuntu noble InRelease
Hit:3 http://archive.ubuntu.com/ubuntu noble-updates InRelease
Hit:4 http://archive.ubuntu.com/ubuntu noble-backports InRelease
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
All packages are up to date.

安装并配置Docker#

由于我只需要使用容器和镜像功能,只需要安装下列软件包即可:

Terminal window
apt install docker.io

安装完成后输入docker info可以查看具体的docker版本信息:

Terminal window
root@HZW001-GF3TFYJ6:~# docker info
Client:
Version: 29.1.3
Context: default
Debug Mode: false
Plugins:
trust: Manage trust on Docker images (Docker Inc.)
Version: 29.1.3
Path: /usr/libexec/docker/cli-plugins/docker-trust
Server:
failed to connect to the docker API at unix:///var/run/docker.sock; check if the path is correct and if the daemon is running: dial unix /var/run/docker.sock: connect: no such file or directory

可以看到最后的Server报错了,docker.sock连接失败,此时执行docker ps也能看到一样的报错信息,原因是非管理员权限以及策略组受限的情况下,WSL的某些启动项不能正常启动,docker守护进程未成功注册,通过运行systemctl查看可以获得信息:

Terminal window
root@HZW001-GF3TFYJ6:~# systemctl
System has not been booted with systemd as init system (PID 1). Can't operate.
Failed to connect to bus: Host is down

检查启动模式配置:

Terminal window
root@HZW001-GF3TFYJ6:~# cat /etc/wsl.conf
[boot]
systemd=true
root@HZW001-GF3TFYJ6:~# ps -p 1
PID TTY TIME CMD
1 ? 00:00:00 init

可以看到默认的[boot]行为是使用systemd启动,但实际上的启动进程确是init,这是因为这台设备外层的Windows用户权限不足以及策略组限制造成的,即使设置了WSL也无法通过systemd启动,不过没关系,我们可以手动启动它;

手动启动Docker守护进程#

在终端中输入dockerd

Terminal window
root@HZW001-GF3TFYJ6:~# dockerd
INFO[2026-07-10T09:21:18.079091942+08:00] Starting up
INFO[2026-07-10T09:21:18.080114149+08:00] containerd not running, starting managed containerd
...
INFO[2026-07-10T09:21:18.610835424+08:00] stopping event stream following graceful shutdown error="<nil>" module=libcontainerd namespace=moby
INFO[2026-07-10T09:21:18.611130724+08:00] stopping healthcheck following graceful shutdown module=libcontainerd
INFO[2026-07-10T09:21:18.611163851+08:00] stopping event stream following graceful shutdown error="context canceled" module=libcontainerd namespace=plugins.moby
failed to start daemon: Error initializing network controller: error obtaining controller instance: failed to register "bridge" driver: failed to add jump rules to ipv4 NAT table: failed to append jump rules to nat-PREROUTING: (iptables failed: iptables --wait -t nat -A PREROUTING -m addrtype --dst-type LOCAL -j DOCKER: Warning: Extension addrtype revision 0 not supported, missing kernel module?
iptables v1.8.10 (nf_tables): CHAIN_ADD failed (No such file or directory): chain PREROUTING
(exit status 4))

可以看到守护进程毫不意外地…失败并退出了,从这个启动日志来看,Docker 启动失败的核心原因是 iptablesnftables 的冲突,导致 Docker 无法在 NAT 表中添加必要的网络规则,系统使用的这两个框架运行在非legacy模式,目前Docker仍然使用传统legacy框架运行,这里我们使用如下指令配置:

Terminal window
update-alternatives --config iptables
update-alternatives --config ip6tables

通过输入数字选项切换到legacy,如下:

Terminal window
root@HZW001-GF3TFYJ6:~# update-alternatives --config iptables
There are 2 choices for the alternative iptables (providing /usr/sbin/iptables).
Selection Path Priority Status
------------------------------------------------------------
* 0 /usr/sbin/iptables-nft 20 auto mode
1 /usr/sbin/iptables-legacy 10 manual mode
2 /usr/sbin/iptables-nft 20 manual mode
Press <enter> to keep the current choice[*], or type selection number: 1
update-alternatives: using /usr/sbin/iptables-legacy to provide /usr/sbin/iptables (iptables) in manual mode
root@HZW001-GF3TFYJ6:~# update-alternatives --config ip6tables
There are 2 choices for the alternative ip6tables (providing /usr/sbin/ip6tables).
Selection Path Priority Status
------------------------------------------------------------
* 0 /usr/sbin/ip6tables-nft 20 auto mode
1 /usr/sbin/ip6tables-legacy 10 manual mode
2 /usr/sbin/ip6tables-nft 20 manual mode
Press <enter> to keep the current choice[*], or type selection number: 1
update-alternatives: using /usr/sbin/ip6tables-legacy to provide /usr/sbin/ip6tables (ip6tables) in manual mode

配置完成后,再次执行dockerd

Terminal window
root@HZW001-GF3TFYJ6:~# dockerd
INFO[2026-07-10T10:00:09.573861747+08:00] Starting up
INFO[2026-07-10T10:00:09.576029128+08:00] containerd not running, starting managed containerd
...
INFO[2026-07-10T10:00:11.654473082+08:00] Docker daemon commit="29.1.3-0ubuntu3~24.04.2" containerd-snapshotter=true storage-driver=overlayfs version=29.1.3
INFO[2026-07-10T10:00:11.654841516+08:00] Initializing buildkit
INFO[2026-07-10T10:00:11.689795121+08:00] Completed buildkit initialization
INFO[2026-07-10T10:00:11.695251753+08:00] Daemon has completed initialization
INFO[2026-07-10T10:00:11.695354129+08:00] API listen on /var/run/docker.sock

可以看到守护进程启动成功,开始侦听/var/run/docker.sock;此时保持这个守护进程运行,新建一个终端进入WSL,再次尝试执行docker ps,可以看到成功唤起docker容器状态回显:

Terminal window
root@HZW001-GF3TFYJ6:~# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES

此时已经可以正常部署任意容器,但还无法使用--gpus选项为容器配置GPU调用,并且需要占用一个前台终端来运行dockerd守护进程,使用Ctrl+C或在其他终端中执行pkill dockerd即可关闭守护进程;

安装和使用screen(可选)#

screen组件是Linux/Unix系统下的一个终端多路复用器(Terminal Multiplexer)。它的核心作用是让你在一个物理终端里,管理多个独立的虚拟终端会话,简单来说,类似给终端”分屏”了,且后台挂起的虚拟终端不会因为物理终端的结束而杀掉虚拟终端内进行的任务,非常方便,这里我们使用它来托管dockerd守护进程;

使用如下指令安装screen组件:

Terminal window
apt install screen

这个组件的基础指令如下:

Screen Command
1. screen -S <Name> 创建名为<Name>的终端
2. screen -ls 查看screen创建的终端列表
3. screen -r <Name> 连接到对应的终端列表
4. screen -D <Name> 强制清除名为<Name>终端的占用
5. screen -D -RR <Name> 强制抢占对应的终端

这里可以创建一个虚拟终端,并在其内部执行dockerd,使用Ctrl + A + D快捷键可以退出虚拟终端回到前台,如果遇到守护进程占用的问题,可以先使用pkill dockerd杀死进程后再运行守护进程:

Terminal window
root@HZW001-GF3TFYJ6:~# dockerd
INFO[2026-07-10T11:12:27.175032223+08:00] Starting up
failed to start daemon, ensure docker is not running or delete /var/run/docker.pid: process with PID 1756 is still running
root@HZW001-GF3TFYJ6:~# pkill dockerd
root@HZW001-GF3TFYJ6:~# dockerd
INFO[2026-07-10T11:12:36.630155346+08:00] Starting up
...
INFO[2026-07-10T11:12:40.115622493+08:00] Completed buildkit initialization
INFO[2026-07-10T11:12:40.120712586+08:00] Daemon has completed initialization
INFO[2026-07-10T11:12:40.120782351+08:00] API listen on /var/run/docker.sock

顺带一提,在screen虚拟终端中默认是不启用滚轮和↑↓键支持的,可以通过配置文件的方式启用,但如果不想配置直接查看前面的日志输出内容,按下Ctrl + A + [进入Cpoy Mode后就可以上下查看了;

Docker换源(可选)#

编辑/etc/docker/daemon.json,添加一些镜像源后重启dockerd,如果没有这个文件就在该位置创建一个:

/etc/docker/daemon.json
{
"registry-mirrors": [
"https://docker.m.daocloud.io",
"https://docker.imgdb.de",
"https://docker-0.unsee.tech",
"https://docker.hlmirror.com",
"https://docker.1ms.run",
"https://func.ink",
"https://lispy.org",
"https://docker.xiaogenban1993.com"
]
}

当然如果你的设备上有梯子的话也可以不用这一步,不过在受限设备的环境下通常也没法访问这些外部镜像源,此时可以使用docker镜像导出、导入功能来部署,也可以使用一台常规设备完成整个环境部署后直接导出整个WSL镜像,再在目标设备中加载它(后面会提到)

CAUTION

注意,上述提到的源不一定一直有效,请根据实际情况搜索可用的源

配置NVIDIA容器运行时#

Docker本体的配置工作完成,接下来需要完成NVIDIA容器运行时,在此之前,最好先在Windows环境下安装NVIDIA APP并安装较新的驱动(主要是我需要使用较新版本的CUDA),然后在Windows终端和WSL中分别运行nvidia-smi,确保能看到一致的GPU信息、CUDA版本:

NVIDIA-GPU详情

如果没有容器运行时会怎样?#

简单拉取了ubuntu:latest镜像,添加--gpu all参数运行:

Terminal window
root@HZW001-GF3TFYJ6:~# docker images
IMAGE ID DISK USAGE CONTENT SIZE EXTRA
ubuntu:latest b7f48194d4d8 160MB 45.3MB
root@HZW001-GF3TFYJ6:~# docker run --rm --gpus all ubuntu
docker: Error response from daemon: could not select device driver "" with capabilities: [[gpu]]
Run 'docker run --help' for more information

可以看到启动失败,报错信息提示没有找到任何GPU,执行以下指令查看当前的运行时:

Terminal window
docker info | grep -i 'Runtimes\|Default Runtime'

除了一些常规的警告信息外,有如下两条:

  • Runtimes: io.containerd.runc.v2 runc
  • Default Runtime: runc

默认运行时为runc,这是一个标准OCI运行时实现,此时只有CPU支持,带有--gpus参数的启动指令执行失败就是这个原因,当前的docker运行时不支持识别GPU;

配置NVIDIA官方镜像源#

这一块主要参考NVIDIA官方文档:

首先是需要用到的软件工具包:ca-certificatescurl gnupg2,对于我使用的发行版似乎不需要特别安装,但如果某些发行版没有自带这些工具,可以参考官方给出的手动安装指令:

Terminal window
sudo apt-get update && sudo apt-get install -y --no-install-recommends ca-certificates curl gnupg2

在这之后,使用如下指令添加NVIDIA官方源:

Terminal window
curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey | sudo gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg \
&& curl -s -L https://nvidia.github.io/libnvidia-container/stable/deb/nvidia-container-toolkit.list | \
sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g' | \
sudo tee /etc/apt/sources.list.d/nvidia-container-toolkit.list

受限于网络原因,可能不会一次成功,重复运行这条指令时会提示覆盖gpg密钥,输入y确认;
最后需要看到两条deb源输出,否则添加失败

Terminal window
File '/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg' exists. Overwrite? (y/N) y ↵
deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://nvidia.github.io/libnvidia-container/stable/deb/$(ARCH) /
#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://nvidia.github.io/libnvidia-container/experimental/deb/$(ARCH) /

执行apt update拉取源信息,可以看到NVIDIA相关的镜像源:

Terminal window
root@HZW001-GF3TFYJ6:~# apt update
...
Ign:1 https://nvidia.github.io/libnvidia-container/stable/deb/amd64 InRelease
...
Ign:1 https://nvidia.github.io/libnvidia-container/stable/deb/amd64 InRelease
...

安装 NVIDIA Container Toolkit#

配置并更新源后,使用如下指令安装:

Terminal window
apt install nvidia-container-toolkit

可能会遇到ldconfig报错无法建立软链接的问题,原因是WSL的部分驱动目录是从Windows中只读映射过来的,在WSL中属于正常现象,忽略即可:

Terminal window
/sbin/ldconfig.real: Can't link /usr/lib/wsl/lib/libnvoptix_loader.so.1 to libnvoptix.so.1
/sbin/ldconfig.real: Can't link /usr/lib/wsl/lib/libnvidia-gpucomp.so.610.43.02 to libnvidia-gpucomp.so

安装完成后运行nvidia-ctk配置docker运行时:

Terminal window
nvidia-ctk runtime configure --runtime=docker

随后需要重启dockerd守护进程,重启完成后查看docker运行时信息:

Terminal window
root@HZW001-GF3TFYJ6:~# docker info | grep -i 'Runtimes\|Default Runtime'
...
Runtimes: io.containerd.runc.v2 nvidia runc
Default Runtime: runc
...

可以看到Runtimes中存在nvidia,配置完成;

测试容器GPU调用#

由于Default默认运行时是runc,运行容器时应该加上--runtime nvidia参数,这里我们再次运行容器,并在容器中执行nvidia-smi

Terminal window
root@HZW001-GF3TFYJ6:~# docker images
IMAGE ID DISK USAGE CONTENT SIZE EXTRA
ubuntu:latest b7f48194d4d8 160MB 45.3MB
root@HZW001-GF3TFYJ6:~# docker run --rm --runtime nvidia --gpus all ubuntu nvidia-smi
Fri Jul 10 07:53:48 2026
+-----------------------------------------------------------------------------------------+
| NVIDIA-SMI 610.43.02 KMD Version: 610.47 CUDA UMD Version: 13.3 |
+-----------------------------------------+------------------------+----------------------+
| GPU Name Persistence-M | Bus-Id Disp.A | Volatile Uncorr. ECC |
| Fan Temp Perf Pwr:Usage/Cap | Memory-Usage | GPU-Util Compute M. |
| | | MIG M. |
|=========================================+========================+======================|
| 0 NVIDIA T600 Laptop GPU On | 00000000:01:00.0 Off | N/A |
| N/A 66C P8 N/A / 5001W | 127MiB / 4096MiB | 0% Default |
| | | N/A |
+-----------------------------------------+------------------------+----------------------+
+-----------------------------------------------------------------------------------------+
| Processes: |
| GPU GI CI PID Type Process name GPU Memory |
| ID ID Usage |
|=========================================================================================|
| No running processes found |
+-----------------------------------------------------------------------------------------+

如果一切顺利,应该能够看到和WSL及Windows中执行nvidia-smi看到的结果一致;

这里我拉取了ghcr.io/ggml-org/llama.cpp:server-cuda12部署了一个本地大模型进行测试,模型选取Gemma-4-E2BQ4_K_M量化,32K上下文:

Gemma-4-E2B模型部署 模型生成效果及GPU占用

显存占用在2~3G左右,生成速度约 30 Token/s,用来做代码自动补全等简单任务还是绰绰有余的,但是需要智商的场合,这台设备还是太捉襟见肘了;

WSL文件系统镜像#

发行版的默认安装位置在%LOCALAPPDATA%\Package\,其中%LOCALAPPDATA%默认情况下指代的位置通常是C:\Users\<用户名>\AppData\Local,然后在这个文件夹内搜索发行版的名称(例如此处是Ubuntu),可以找到对应的目录,在下属LocalState文件夹中能够找到对应发行版的文件系统虚拟磁盘镜像:

默认安装位置文件系统镜像

磁盘镜像导出、迁移#

虚拟磁盘镜像默认放在C盘还是太容易因容器膨胀导致可用空间不足了,不过可以将磁盘镜像归档导出,移动到其它位置,首先需要在Windows终端中运行wsl --shutdown停止所有子系统镜像,然后使用wsl --list列出活动的的发行版镜像列表:

Terminal window
PS > wsl --list ↵
适用于 Linux 的 Windows 子系统分发版:
Ubuntu-24.04 (默认)

执行wsl --export <活动镜像名> <.tar归档文件路径>可以导出镜像到指定位置,例如执行如下指令:

Terminal window
wsl --export Ubuntu-24.04 D:\WSL2\Ubuntu-24.04.tar

此时会将Ubuntu-24.04的文件系统镜像打包并输出到D:\WSL2\Ubuntu-24.04.tar

导出完成后,可以使用wsl --unregister <活动镜像名>注销镜像,被注销的镜像对应的文件系统虚拟磁盘会被删除,例如这里执行wsl --unregister Ubuntu-24.04后,先前找到的ext4.vhdx会被删除;

导入镜像的指令如下:

Terminal window
wsl --import <活动镜像名称> <虚拟磁盘存放位置> <镜像归档文件> --version <WSL版本>

活动镜像名称可以自行设定,WSL版本选择WSL2,例如此时我执行的指令如下:

Terminal window
wsl --import Ubuntu-24.04-LTS D:\WSL2\ D:\WSL2\Ubuntu-24.04.tar --version 2

文件系统虚拟磁盘镜像会被挂载到D:\WSL2\,此时在终端中输入wsl --list可以看到注册的镜像,执行wsl默认会以root身份登入,如果希望修改默认用户可以进行配置,但是这里我有且只有一个root用户,就不做展开了;

虚拟磁盘空间清理#

vhdx虚拟磁盘的设计是只扩张不缩小,当发行版镜像需要更多空间时,虚拟磁盘镜像会自动扩容,占用的物理磁盘空间会越来越大,但是在WSL内删除文件时,这部分空间并不会被直接释放,常规方法是在使用wsl --shutdown停止所有发行版运行后,通过diskpart等工具对虚拟磁盘镜像进行压缩,但是在Windows中操作系统自带了一个叫做Optimize-VHD的工具,原理是先进入发行版中将空闲磁盘部分写入无用标记,然后在停止发行版运行后通过该工具对标记空间进行清理,达到给虚拟磁盘瘦身的效果;

使用如下指令即可在发行版镜像中标记空闲磁盘空间:

Terminal window
sudo fstrim -v /

这里要注意的是,由于部分发行版mount映射了Windows的物理磁盘空间,所以这里执行后标记的无用空间会接近真正的物理磁盘空间空间+虚拟磁盘内的空闲空间大小,这是正常的,不用理会,一切就绪后先回到外部终端中使用wsl --shutdown结束所有发行版运行,然后找到对应的vhdx虚拟磁盘镜像文件,以管理员身份打开终端,输入以下指令即可:

Terminal window
Optimize-VHD -Path "你的VHDX文件完整路径" -Mode Full

压缩虚拟磁盘空间

对于使用WSL作为后端的Docker Desktop镜像后端,一样可以使用这种方式解决docker_data.vhdx体积膨胀的问题,在多发行版镜像共存的情况下,使用wsl -d <发行版名称>进入对应的发行版,然后执行空闲磁盘空间标记,之后按照同样的流程对vhdx虚拟磁盘文件进行瘦身即可;

WSL2内部运行Docker并配置NVIDIA GPU
https://blog.luchichick.cn/posts/2026/07/wsl2内部运行docker并配置nvidia-gpu/
作者
LuChiChick
发布于
2026-07-09
许可协议
CC BY-NC-SA 4.0