在 Windows 上跑 Docker,早几年最顺手的方案是装 Docker Desktop 的 Hyper-V 后端。但用过的人都知道,Hyper-V 一开,整个系统像背了个沙袋,虚拟机管理器的开销明晃晃挂在那,内存动不动被吃掉好几个 G,笔记本风扇转得跟飞机起飞似的。后来 Docker Desktop 默认切到 WSL 2 后端,Windows 下跑容器的体验才算真正轻下来——容器跑在 WSL 2 的轻量虚拟机里,和 Windows 共享内核调度,启动快、占用小、和本地文件系统交互也自然。这篇文章就用 WSL 把 Docker 从零搭起来,覆盖环境准备、安装选型、核心配置、常用容器部署,以及一堆我在实际部署中踩过的报错和排查思路,适合想在 Windows 上低成本跑 Docker 的开发者、测试和运维朋友参考。
1. 为什么把 WSL 当成 Docker 的底座:先搞清这几层关系
很多人第一次听说"WSL 装 Docker"会有点绕:Docker 不是装 Windows 版吗,怎么又扯上 WSL 了?这里得把几层关系捋清楚,后面配置才不会晕。
1.1 Docker Desktop 与 WSL 2 的分工逻辑
Docker Desktop 在 Windows 上其实是一个壳,壳里面真正负责跑容器的是一个 Linux 虚拟机。早前这个虚拟机基于 Hyper-V,现在默认基于 WSL 2。WSL 2 本身就是微软做的一个轻量虚拟机,底层是真正的 Linux 内核,不是翻译层。Docker Desktop 做的事情是:把你 Windows 里的 Docker 命令行、图形界面转发到这个 WSL 2 虚拟机里,由虚拟机里的 containerd、dockerd 去实际创建和运行容器。
所以可以这样理解:WSL 2 是地基,Docker 引擎是房子。Windows 上你敲docker ps,实际上是在和一个跑在 WSL 2 里的 Linux Docker 引擎通信。也正因为 Docker 引擎在 Linux 里跑,所有镜像、容器、网络模型都是原生 Linux 环境,和服务器上的 Docker 行为完全一致,开发环境和生产环境之间的差距被压到最低。
1.2 WSL 1 和 WSL 2 的差别:为什么必须用 WSL 2
WSL 有两个大版本。WSL 1 是把 Linux 系统调用翻译成 Windows 系统调用,没有真正的 Linux 内核;WSL 2 则是跑一个完整的轻量虚拟机,里面有原生 Linux 内核。对 Docker 来说,WSL 1 基本没法用——Docker 引擎依赖 cgroups、namespaces、overlayfs 这些内核特性,WSL 1 的翻译层扛不住。所以现在装 Docker Desktop 时,它会强制要求 WSL 2 内核开启。如果你机器上还留着旧版 WSL 1 的发行版,建议直接删除重装,或者升级到 WSL 2。
判断某个发行版用的是哪版 WSL,终端里执行wsl --list --verbose,VERSION 那列是 2 就没问题,是 1 的可以执行wsl --set-version <发行版名> 2做转换,转换过程要几分钟,耐心等。
1.3 和纯 Hyper-V 方案、VirtualBox 方案对比
再说说为什么这套组合值得用。之前常用的方案无非三种:
| 方案 | 优点 | 缺点 |
|---|---|---|
| Docker Desktop + Hyper-V | Windows 原生支持,Docker 官方维护 | 系统整体占用高,必须开启 Hyper-V 导致其他虚拟机软件受限 |
| Docker Desktop + WSL 2 | 启动快、内存按需分配、文件互访方便 | 需要对 WSL 2 有一定了解,排错时多了一层 |
| VirtualBox 里跑 Linux 再装 Docker | 隔离彻底、可完全自定义 | 管理成本高,性能和体验断档,不能直接用 Windows 侧 Docker CLI |
实测下来,WSL 2 后端的启动速度明显比 Hyper-V 后端快,冷启动一个容器基本 1 秒内完成;而且 WSL 2 的内存是动态占用,容器不忙时不会吃掉全部配额。日常开发、本地中间件模拟、CI 脚本验证,这套组合是最省心的,这也是我推荐它的核心原因。
2. 从零搭建:WSL 环境准备与 Docker Desktop 安装
标题叫"快速部署",但该做的准备一步都不能省。跳过环境检查和版本确认,后面报错会教你做人。下面按我实际操作的顺序来。
2.1 开启 Windows 功能与 WSL 2 内核更新
第一步,打开"控制面板 - 程序和功能 - 启用或关闭 Windows 功能",勾选以下三项:
- 适用于 Linux 的 Windows 子系统
- 虚拟机平台
- 虚拟机监视程序平台(Windows 10 某些版本需要,Windows 11 一般不需要)
勾完重启系统。接着以管理员身份打开 PowerShell,执行:
wsl --install这个命令在较新的 Windows 10/11 上会自动安装 WSL 2 内核并设置默认版本为 2。如果执行完提示需要手动下载内核,去微软官方文档的 WSL 页面下载wsl_update_x64.msi安装即可。装完验证一下:
wsl --status看到"默认版本:2"之类的输出就说明基础环境就绪。
2.2 安装发行版与迁移到非系统盘
wsl --install默认会装 Ubuntu(通常是当前 LTS 版)。如果系统盘空间紧张,建议把发行版放到其他盘。这一步很多人忽略,等 WSL 的 VHD 镜像长到几十 G 才想起来挪,那时候就要用导出导入,过程略麻烦。
先查看当前已安装的发行版:
wsl --list --verbose然后导出、注销、再导入到目标目录:
wsl --export Ubuntu D:\wsl\ubuntu.tar wsl --unregister Ubuntu wsl --import Ubuntu D:\wsl\ubuntu D:\wsl\ubuntu.tar --version 2注意--import之后默认以 root 登录,且默认用户不是原来的普通用户。需要用ubuntu.exe config --default-user <用户名>恢复默认用户,或者编辑/etc/wsl.conf设置[user] default=<用户名>。新装发行版就不存在这个问题,直接开箱。
安装完成后进入 WSL 里确认一下内核版本:
uname -r cat /etc/os-release内核版本至少在 5.10 以上,Docker 用起来才顺手。
2.3 Docker Desktop 安装选项与 WSL 集成设置
去 Docker 官网下载 Docker Desktop Installer.exe 安装。安装过程中有一步是选择使用的后端,务必勾选"Use WSL 2 based engine"。如果漏勾了,装完也可以在 Settings > General 里勾回来。
装完打开 Docker Desktop,进入 Settings > Resources > WSL Integration,这里会列出所有已安装的 WSL 发行版。默认情况下,Docker Desktop 只集成默认发行版。如果你希望某个发行版里也能直接用 Docker CLI,就把对应的开关打开。我习惯把Ubuntu打开,这样我进入 WSL 的 Ubuntu 终端后,直接敲docker就能用,不需要在 WSL 里再装一遍 Docker 引擎,因为引擎是共享的。
还有一个常被忽略的点:Docker Desktop 自带的 CLI 工具路径在 Windows 侧是C:\Program Files\Docker\Docker\resources\bin,里面包含docker.exe、docker-compose.exe。WSL 集成打开后,WSL 内的/usr/bin/docker其实是一个客户端包装,它会自动和 Windows 侧的 Docker Desktop 通信。所以 WSL 里不需要apt install docker.io,装了反而可能出现版本不一致的混乱,我见过好几个同事因为这个排查半天。
3. 装好之后的三个关键配置:镜像源、资源上限、存储位置
Docker Desktop 装完就能用,但"能用"和"好用"之间差着三个配置。这三件事我每次在新机器上配 Docker 都会做,属于典型的过来人经验。
3.1 镜像加速配置
在国内网络环境下,拉镜像慢是很普遍的问题。Docker Desktop 的镜像源配置在 Settings > Docker Engine 里,JSON 配置里加registry-mirrors字段:
{ "registry-mirrors": [ "https://docker.m.daocloud.io", "https://dockerproxy.com" ] }保存后 Docker 引擎会重启。注意,不同镜像加速地址的可用性会变化,建议不要只填一个。另外提醒一句,不要轻信网上随便给的加速地址,优先选知名云服务商或开源社区维护的镜像源。配置好之后,拉一个大镜像试试速度,比如docker pull mysql:8.0,明显变快就说明生效了。
3.2 .wslconfig 的资源控制
Docker Desktop 虽然能看到内存/CPU 设置,但 WSL 2 整体资源伸缩范围其实由C:\Users\<用户名>\.wslconfig控制。这个文件是全局生效的,不只影响 Docker,还影响所有 WSL 发行版。我的一份经典配置如下:
[wsl2] memory=6GB processors=4 swap=2GB localhostForwarding=truememory设置 WSL 2 虚拟机最大能拿多少内存,processors限制可用 CPU 核数,swap是交换分区大小。这里要特别说明:WSL 2 默认会占用主机最多 50% 的内存,如果主机是 32G 内存,WSL 2 理论上最高可以吃 16G,对开发机来说太吓人了。所以显式限制 memory 非常有必要,宁可偶尔不够用再调大,也不要让它无声无息吃满内存。
改完.wslconfig后,需要执行wsl --shutdown让配置生效,然后重启 Docker Desktop。
3.3>{ "data-root": "D:\\docker-data" }
保存后 Docker 会用新目录存数据。旧目录的数据需要提前docker save导出或者在停机状态下直接拷贝,但更稳妥的做法是把旧数据删掉重新拉镜像,因为大多数场景下镜像拉取比重建数据成本低。
第二,定期压缩 VHD。操作流程:
wsl --shutdown # 打开磁盘管理,找到 docker_data.vhdx 对应的虚拟磁盘 # 或者用 diskpart 执行 compact更省事的方法是直接在管理员 PowerShell 里跑 diskpart 的 compact 命令,或者用 Optimize-VHD。我个人的习惯是每个月做一次:先docker system prune -a清理悬空镜像和未使用数据,然后wsl --shutdown,再压缩 VHD。
4. 实战:在 WSL 里跑 MySQL 和 Redis,验证 WSL 后端
配置讲再多,不如跑两个容器来得直接。这里选 MySQL 和 Redis 是因为它们是最常见的本地中间件,几乎每个后端项目都离不开。这两个容器跑起来,WSL 后端的网络、卷、端口映射也就全验证了。
4.1 MySQL 8.0 容器部署
先创建一个自定义网络,方便后面容器用容器名互访:
docker network create dev-network再跑 MySQL 容器:
docker run -d \ --name mysql-dev \ --network dev-network \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORD=root123 \ -v mysql-data:/var/lib/mysql \ mysql:8.0解释一下关键点:-v mysql-data:/var/lib/mysql是把 MySQL 的数据持久化到 Docker 卷里,容器删了数据还在。如果忘了加这行,容器一删,数据库内容全没,这个坑我踩过不止一次。-e MYSQL_ROOT_PASSWORD是初始化 root 密码的快捷方式,第一次启动时生效。
启动后验证:
docker ps docker exec -it mysql-dev mysql -uroot -proot123 -e "SELECT VERSION();"从 Windows 侧验证端口映射:用 Windows 的命令行工具连 MySQL,或者直接用浏览器访问localhost:3306看是否能通。WSL 2 的localhostForwarding默认开启,所以 Windows 的localhost:3306可以直接转发到 WSL 里的容器端口,这比早期 WSL 1 时代要手动配端口转发舒服太多。
4.2 Redis 主从部署
单机 Redis 不过瘾,顺便把主从也搭起来,顺便验证自定义网络的容器名互相解析能力。先拉镜像:
docker pull redis:7起主节点和从节点:
docker run -d --name redis-master --network dev-network -p 6379:6379 redis:7 redis-server --appendonly yes docker run -d --name redis-slave --network dev-network -p 6380:6379 redis:7 redis-server --slaveof redis-master 6379注意从节点的--slaveof用的是容器名redis-master,这正是自定义网络里 Docker DNS 的功劳。如果两个容器都在默认网络,也能通过容器名访问,但自定义网络更干净,而且可以和 MySQL 等容器一起管理。
进入从节点验证主从状态:
docker exec -it redis-slave redis-cli INFO replication看到role:slave和master_link_status:up就说明主从建立成功。整个部署过程中,你可以在 Docker Desktop 的仪表盘里实时看到容器状态和资源占用,WSL 后端的启动速度在这里体现得很直观——容器秒级起停,不会有任何拖沓感。
4.3 从 Windows 访问容器服务
前面提过,localhostForwarding=true让 Windows 可以直接访问 WSL 里的服务。但我需要提醒一点:如果你改了.wslconfig里的localhostForwarding=false,或者有人在 remote 环境访问,那就得改用 WSL 虚拟机的 IP 地址。查看这个地址可以进入 WSL 执行:
hostname -I然后用浏览器或客户端访问http://<WSL-IP>:端口即可。另外,Windows 防火墙偶尔会拦截外部设备(比如手机或另一台电脑)访问 WSL 里的服务,这时候需要在防火墙里放行对应端口。这是很多人在局域网联调时突然发现连不上的最常见原因。
5. 高频报错排查链路:从"容器对象访问被拒绝"到"daemon 起不来"
WSL 配 Docker 多数问题集中在安装阶段和启动阶段。我把网上高频提问的热词收集了一遍,整理成几个典型排查链路,每个都给出可复现的操作过程。
5.1 WSL 安装组件存储已损坏:如何修复
Windows 更新或者第三方工具清理系统文件时,可能把 WSL 组件搞残。典型症状:wsl --install报错、wsl命令无响应,或者打开 WSL 终端提示"安装组件存储已损坏"。这个问题的排查链路比较固定:
第一步,以管理员身份运行 PowerShell,执行:
wsl --status如果能输出正常状态,说明 WSL 内核没问题,问题多在某个发行版上;如果直接报错,则先修 WSL 组件。
第二步,修复组件存储。在 Windows 10/11 上,先尝试:
sfc /scannow dism /online /cleanup-image /restorehealth这两个命令执行耗时较长,SSD 上大约 10 到 30 分钟,耐心跑完。跑完重启再试wsl --status。
第三步,如果系统文件检查没用,就重装 WSL 本身。先卸载当前 WSL:
wsl --uninstall然后重新:
wsl --install这里补充一句:wsl --uninstall不会删除已经导入的发行版数据,所以不用担心数据丢失。如果你的发行版是通过--import导入到其他盘的,重新安装 WSL 后还需要重新执行一次wsl --import注册,或者用wsl --attach之类的操作恢复。
第四步,仍不行就在"启用或关闭 Windows 功能"里取消"适用于 Linux 的 Windows 子系统"和"虚拟机平台",重启,再重新勾选、重启。这个步骤会强制系统重新配置相关组件,成功率很高。
5.2 Docker Desktop 启动失败:提示 virtualization support not detected
这个报错直译是"未检测到虚拟化支持"。常见误区是一看到这句话就以为是 WSL 坏了,其实根源往往是主板 BIOS 里的虚拟化开关被关了,或者 Windows 的虚拟机监控程序没有生效。
排查顺序:
第一,检查 Hyper-V 是否开启:
systeminfo看输出里的 Hyper-V 要求部分,如果四个选项都是"是",说明系统层面虚拟化可用;如果显示"已检测到虚拟机监控程序。将不显示 Hyper-V 所需的功能",这是 Hyper-V 正在运行时的正常提示。
第二,检查 BIOS 虚拟化设置。开机按 F2/F12/Del 进入 BIOS,找到Intel Virtualization Technology/AMD SVM Mode,确保开启,保存退出,进入系统后再试。很多品牌机默认关闭这个选项,尤其是工作站级别的机器。
第三,确认没有第三方虚拟机软件和 Hyper-V 冲突。VirtualBox 5.x 老版本和 Hyper-V 同时开会导致启动失败,Vagrant 配的 VirtualBox 也常见这个问题。如果你装了 VirtualBox,建议升级到 6.x 以上或暂时卸载测试。
第四,Windows 11 的 VBS(基于虚拟化的安全)会影响授权。如果系统开了内核隔离(内存完整性),WSL 2 和 Docker Desktop 的启动速度会变慢甚至报错。可以临时关闭"内核隔离"测试,路径在 Windows 安全中心 > 设备安全性 > 内核隔离。
5.3 无法枚举容器中的对象,访问被拒绝
这个报错常在 Docker Desktop 挂载 Windows 目录到容器时出现。典型场景:docker run -v C:\project:/app ...,然后容器内访问挂载的目录时报"无法枚举容器中的对象"或者"访问被拒绝"。
根因:Windows 文件系统权限和 Docker Desktop 的共享文件驱动(gRPC-FUSE)之间的权限映射冲突。最常见的是挂载了系统盘根目录或者 Windows 用户目录以外的路径,而 Docker Desktop 没有权限读取。
排查和解决:
第一步,确认挂载路径在 Docker Desktop 的共享资源范围内。打开 Docker Desktop Settings > Resources > File Sharing,确保你挂载的 Windows 路径在里面。Windows 盘符路径默认差不多都加了,但如果你直接挂载C:\这种根目录,权限模型很容易崩。
第二步,改用 Docker 卷或 WSL 路径。如果只是代码目录挂载,建议把项目放在 WSL 文件系统里(比如~/project),然后挂载/home/<user>/project而不是/mnt/c/Users/...。WSL 路径的 IO 性能远高于/mnt/c路径,这也是一个性能优化的关键点,后面会细说。
第三步,如果必须挂载 Windows 目录,调整容器内权限:
docker run -v C:\project:/app --user root ...或者避免使用 Windows 文件系统作为代码挂载盘,改成构建镜像时 COPY 进镜像内,减少运行时对 Windows 目录的依赖。
5.4 error: start the windows daemon from a non-elevated terminal
这个报错字面意思:从非提升的终端启动 Windows 守护进程。很多人第一次见会懵,其实问题不在 Docker Desktop 本身,而是 Docker CLI 试图启动 Docker Desktop 时发现没有管理员权限,或者 Docker Desktop 根本没有启动。
处理方式很直接:
先启动 Docker Desktop,等左上角鲸鱼图标变绿。如果已经在运行,检查任务栏右下角图标是否有提示 Docker Engine stopped。然后在 PowerShell(普通权限即可)执行:
docker version正常会分别显示 Client 和 Server 的信息。只有 Client 没有 Server,说明引擎没起来。这里提一个很容易被忽略的点:如果 PowerShell 终端曾经以管理员身份打开过,Docker CLI 在某些配置下会误判环境,导致和 Docker Desktop 的通信不稳定。简单粗暴的办法是关掉所有管理员终端,用普通终端执行命令。这在 Windows 的 UAC 环境下是个经典问题。
5.5 端口占用引发的连环问题
WSL 里容器端口和 Windows 端口冲突时,表现不是 Docker 报错,而是容器起来了但服务访问不了,或者 Windows 端口被其他进程占住。排查命令如下:
Windows 侧查端口占用:
netstat -ano | findstr :3306 tasklist | findstr <PID>WSL 侧查端口占用:
ss -tlnp | grep 3306找到占用进程后,要么杀掉,要么改容器映射端口。我的建议是本地开发时尽量用非默认端口映射,比如 MySQL 映射到3307:3306,Redis 映射到6389:6379,减少和本机其他服务的冲突概率。初始就规避,比事后排查省时间。
6. 几个容易被忽略的日常坑:资源占用、文件读写、GPU 场景
再过一段时间你就会发现,Docker 跑起来本身不难了,难的是让它一直稳定地跑,并且在复杂场景下也能保持良好体验。这一节聊几个我用了大半年 WSL + Docker 后,觉得最值得写下来的点。
6.1 WSL 内存和 CPU 占用过高怎么办
前面提过.wslconfig可以限制资源,但限制之后还是可能出现占用波动。最常见的原因是 Docker 容器内 JVM、Golang 编译等操作会一下子申请大量内存,WSL 2 的机制是"用了多少就占多少物理内存"。如果发现 WSL 的 vmmem 进程吃掉太多内存,先看是哪个容器在搞事:
docker stats --no-streamdocker stats输出会列出容器的 CPU 百分比和内存占用。找到吃内存最狠的容器后,可以在启动时就限制:
docker run -d --memory=1g --cpus=1 ...这样容器内进程再怎么膨胀也突破不了 1G 的上限。注意,--memory限制对 Java 这类会主动预申请内存的运行时尤其有效,否则 JVM 可能按宿主机配置把堆内存初始化得很大,白白浪费资源。
如果 vmmem 本身占用很高但容器统计不多,往往是文件缓存或者其他发行版的进程占用。执行wsl --shutdown强制回收内存,或者重启 Docker Desktop 都能缓解。
6.2 跨文件系统的 IO 性能差异:/mnt/c 慢得像外网
WSL 2 里访问 Windows 文件(/mnt/c/...)和访问 WSL 自身文件(/home/...)性能差一个数量级。具体的区别:WSL 2 自身文件在 ext4 文件系统上,跑在虚拟磁盘里;而/mnt/c走的是 9P 协议,一路经过 Windows 到 Linux 的转换。
所以做项目时,代码目录的存放位置非常关键。如果你在 Windows 的编辑器(比如 VSCode)里打开C:\project,同时容器挂载的是/mnt/c/project,你每次编译、打包都会感受到明显的卡顿。标准解法是:
- 开发目录放在 WSL 内部,比如
~/project - 用 VSCode 的 WSL 远程模式直接打开 WSL 里的目录
- 容器挂载时用 WSL 路径,不用
/mnt/c路径
这样做之后,容器内文件监听(webpack、nodemon 这类工具)的性能会明显改善,IO 瓶颈基本消失。
6.3 涉及 GPU 的场景:WSL 里的 CUDA 效率
虽然不在本文核心范围,但"wsl安装cuda"这个热词搜索量很大,值得简单提一嘴。WSL 2 支持 GPU 加速,NVIDIA 官方有个 Windows 上的 CUDA on WSL 方案。核心步骤就两步:Windows 侧装支持 WSL 的显卡驱动,WSL 内装 CUDA Toolkit。然后就能在容器里跑带 CUDA 的工作负载。注意 Docker 容器里跑 GPU 需要额外加参数:
docker run --gpus all ...不过要提醒的是:如果你的主要需求是跑大型机器学习训练,WSL 里的 GPU 容器性能上限还是会受桌面系统资源竞争的影响。轻度验证代码、跑跑小模型,WSL 完全够用;严肃训练还是老实回 Linux 服务器。
6.4 在 VSCode 里无缝使用 WSL 和 Docker 的体验
最后说一个体验提升很大的操作:把开发环境整体搬进 WSL。VSCode 装一个名为"WSL"的扩展,然后远程打开 WSL 里的某个目录,这时候 VSCode 的终端、调试器都在 WSL 里运行,Docker 命令、文件监听全部原生,和直接 ssh 到 Linux 服务器的体验基本一致。再装一个"Dev Containers"扩展,可以直接把 VSCode 附到某个 Docker 容器里,在容器里改代码、跑测试,Windows 这边只留一个轻量的编辑器外壳。
这套组合用下来,最大的感受是:Windows 只是承载了一个图形环境,真正的开发和运行逻辑全部在 Linux 侧,这种"桌面归桌面,开发归开发"的剥离感非常舒服。
文章写到这里,WSL + Docker 的组合拳已经讲得比较透了。我实际用的过程中,最大的体会是大多数报错都出在环境状态不一致上——WSL 内核版本和 Docker Desktop 版本不匹配、虚拟化开关被主板重置、Windows 更新后组件损坏。遇到问题先别急着重装,按文章里的链路一步步排查,多数都能在十分钟内定位。如果让我总结一条最实用的建议,那就是把.wslconfig、镜像加速、数据盘迁移这三件事在新环境里第一时间做好,之后再慢慢按需加东西,这套组合长期用下来会稳定得多。