ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Windows Docker Desktop 安装配置与自制镜像排错指南

Windows Docker Desktop 安装配置与自制镜像排错指南 在 Windows 上把 Docker Desktop 装起来再从头做出一个属于自己的镜像这件事听起来像是半小时就能收工的活但真正动过手的人多半在第一步就卡住了装完之后托盘图标一直转圈弹一句 virtualization support not detected好不容易起来了拉个镜像卡在 pulling fs layer 十几分钟不动等镜像终于跑起来又发现 C 盘莫名其妙少了几十 G。这些坑我基本一个没落地踩过一遍所以这篇就把 Windows 环境下 Docker Desktop 的安装、配置、镜像制作和排错整条链路摊开讲清楚。不管你是第一次接触容器还是已经会用 docker run 但从来没自己写过 Dockerfile只要手上有台 Win10 或 Win11 的机器跟着走一遍就能得到一个能跑、能改、能分享的自制镜像。1. 动手前的整体方案设计为什么是 Docker Desktop 加 WSL21.1 Windows 上跑容器的三条路为什么最后选了 Docker DesktopWindows 本身没有 Linux 内核而绝大多数容器镜像又都是基于 Linux 构建的所以想在 Windows 上跑容器本质上要解决的是在哪里跑一个 Linux 内核的问题。围绕这个核心矛盾常见的路子其实有三种第一种是装一台完整的 Linux 虚拟机在里面跑原生 Docker第二种是用 Docker Desktop让它自己管理一个轻量级的 Linux 环境第三种是绕开容器直接用 Windows 原生进程跑服务。第一条路最稳但代价也最大。你得先给虚拟机分配内存、磁盘、网络虚拟机开机要一两分钟宿主机和虚拟机之间的文件互传还得额外配置共享目录。对于只是想快速验证一个镜像、跑一个 Redis 或者本地起个数据库的人来说这套流程太重了每天开关机的等待时间加起来足够泡好几杯茶。第二条路就是 Docker Desktop 的设计思路。它在 Windows 上提供一套完整的图形界面和命令行工具背后自动帮你维护一个 Linux 运行环境你只需要关心docker build和docker run这两条命令。安装包直接双击装完重启托盘里出现一条小鲸鱼图标就算成了。对于开发、测试、学习这几个场景这个成本几乎是所有方案里最低的。第三条路听着省事但坑更深。有些服务确实有 Windows 原生版本比如 Redis 早年有社区编译的 Windows 版本但版本普遍落后而且和 Linux 上的行为存在差异你在本地跑通了丢到服务器上照样出问题。容器的价值恰恰在于我本地怎么跑线上就怎么跑用原生版本等于把这份一致性主动放弃了。所以我的判断很直接只要你的目标是开发、调试、学习、做镜像Windows 上的第一选择就是 Docker Desktop。它把虚拟化这层复杂度封装掉了你付出的是几个 G 的磁盘和一点内存占用换回来的是跨平台一致的行为。这笔账怎么算都划算。1.2 WSL2 后端和 Hyper-V 后端该怎么选Docker Desktop 在 Windows 上支持两种后端很多人装的时候看到安装向导里那个勾选框随手就点了下一步其实这个选择会直接影响后续的使用体验。两种方式的核心区别在于容器跑在哪个 Linux 环境里。WSL2 后端是把容器跑在适用于 Linux 的 Windows 子系统里这是微软官方提供的轻量级虚拟化方案启动快、内存占用可以动态伸缩。Hyper-V 后端则是走传统的虚拟机路线功能更全但资源占用更重而且会跟 VMware、VirtualBox 这类软件抢虚拟化层。对比维度WSL2 后端Hyper-V 后端启动速度秒级随用随启分钟级需要完整启动虚拟机内存占用按需分配可动态回收启动即占用固定内存文件性能Windows 目录挂载进容器较慢Linux 侧目录很快挂载性能稳定但整体偏慢与第三方虚拟机共存基本无冲突需要 Hyper-V 独占虚拟化层系统版本要求Win10 2004 及以上内部版本 19041或 Win11专业版、企业版、教育版适用场景日常开发、学习、镜像构建需要完整虚拟机能力的特殊场景表格里的信息一眼就能看出来除非你有明确的理由必须用 Hyper-V否则直接选 WSL2。家庭版 Windows 也只能选 WSL2因为 Hyper-V 本身就不对家庭版开放。我自己在两台机器上分别试过这两种后端WSL2 的体验优势非常明显尤其是从休眠恢复之后容器几乎立刻就能用Hyper-V 那边还在慢慢磨。提示安装向导里那个 Use WSL 2 instead of Hyper-V 的勾选项在支持 WSL2 的机器上默认就是勾上的别手抖取消掉。1.3 装之前先确认的四件事很多人装失败问题其实出在安装之前。花五分钟做一遍前置检查能省下后面两小时的折腾。下面这四项是我每次在新机器上装之前都会过一遍的清单。第一项是系统版本。Win10 需要 2004 及以上也就是内部版本 19041 以上Win11 全系都满足。查看方法是按 WinR 输入winver回车就能看到版本号。如果版本太老先跑系统更新这一步没法跳过。第二项是 CPU 虚拟化。这是最容易被忽略的一环也是那个 virtualization support not detected 报错的根源。任务管理器切到性能标签页看 CPU 那一栏右下角如果写着虚拟化已启用说明没问题如果写的是已禁用就得进 BIOS 打开。具体按键各家主板不一样通常是 Del、F2 或者 F10进去找 Intel VT-x、Intel Virtualization Technology 或者 AMD-V 这类选项设成 Enabled。第三项是磁盘空间和存储位置。Docker Desktop 本体大概 1 到 2 个 G但 WSL2 的数据盘会随着你拉镜像、跑容器不断增长起步预留 30 到 50 个 G 比较从容。更关键的是默认它会装在 C 盘如果你的 C 盘本来就紧张后面会很痛苦所以最好提前规划好放到哪个盘。第四项是 Windows 功能的开启状态。WSL2 依赖两个可选功能适用于 Linux 的 Windows 子系统和虚拟机平台。这两项在后面的安装步骤里会具体操作这里只需要先知道它们是必需品。另外如果你的机器上装了 VMware 或者 VirtualBox装 Docker Desktop 之前建议先确认它们的版本老版本可能会有虚拟化层冲突。2. 环境准备与安装实操从 BIOS 到第一个 hello-world2.1 检查并打开 CPU 虚拟化虚拟化没开是 Windows 装 Docker 最经典的翻车点而且它的表现形式很有迷惑性Docker Desktop 能装完但一启动就报错退出托盘图标转两圈就消失日志里反复出现 virtualization support not detected。很多人第一反应是软件坏了重装了三遍实际上 BIOS 里的开关一动就好了。正确的排查顺序是先看系统层面暴露的状态而不是直接冲进 BIOS。打开任务管理器切到性能标签选左侧的 CPU右侧信息区往下翻能看到虚拟化这一行。显示已启用就跳过这一步显示已禁用再进 BIOS。进 BIOS 的时机是开机自检阶段不同品牌按键不同戴尔和联想常见的是 F2惠普是 F10 或 Esc华硕和微星多是 Del。进去之后不用怕你要找的选项名字通常是这几个之一Intel Virtualization Technology、Intel VT-x、SVM Mode、AMD-V。它一般藏在 Advanced、CPU Configuration 或者 Security 这几个菜单下面。找到之后设为 Enabled按 F10 保存退出。有一点值得说明有些笔记本厂商会把虚拟化选项做成两级开关一级在 BIOS另一级在 Windows 的内核隔离或者内存完整性设置里。如果你 BIOS 明明开了任务管理器还是显示已禁用可以去Windows 安全中心 → 设备安全性 → 内核隔离把内存完整性关掉试试。这个功能会占用虚拟化支持的独占权和 WSL2 有冲突。还有一个细节是已启用但显示虚拟化不可用的情况通常发生在虚拟机里套娃安装比如你在 VMware 里跑了一个 Windows 再想装 Docker。这时候需要在宿主机的虚拟机设置里打开虚拟化 Intel VT-x/EPT 或 AMD-V/RVI的嵌套支持选项。这种场景我建议能避开就避开性能损耗和不稳定性都很明显。2.2 启用 WSL2 与相关 Windows 功能这一步要做的事情本质上是给 Windows 装上一个能被 Docker 使用的 Linux 运行环境。Win10 和 Win11 在操作上略有差别我分开说。Win11 和较新的 Win10 版本已经把安装流程简化成一条命令了。以管理员身份打开 PowerShell执行wsl --install这条命令会自动启用所需的 Windows 功能下载并安装 WSL2 内核和默认的 Ubuntu 发行版然后提示你重启。重启之后系统会让你设置 Linux 用户名和密码随便设一个记得住的就行这个密码后面sudo的时候会用到。如果你的系统版本偏老或者上面这条命令报错那就得手动来。以管理员身份打开 PowerShell依次执行dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart执行完重启电脑然后设置 WSL2 为默认版本并更新内核wsl --set-default-version 2 wsl --updatewsl --update这一条非常重要很多WSL2 安装失败、0x80370102之类的报错根源就是 WSL 内核版本太旧。如果这条命令下载很慢或者失败可以手动去下载内核更新包安装效果一样。装完之后用wsl -l -v验证一下正常应该能看到类似这样的输出NAME STATE VERSION * Ubuntu Running 2关键看 VERSION 那一列是不是 2。如果是 1用wsl --set-version Ubuntu 2转过来。WSL1 和 WSL2 是两个完全不同的实现Docker Desktop 只认 WSL2。注意WSL2 会生成一个虚拟磁盘文件默认放在 C 盘的用户目录下。如果 C 盘空间紧张可以在.wslconfig里配置或者干脆用wsl --export加wsl --import把它迁到别的盘这个后面第 4 章会具体讲。2.3 安装 Docker Desktop 与首次启动配置前置条件齐了之后安装本身反而没什么技术含量。官网下载 Windows 版安装包双击运行安装向导里记得确认勾选 Use WSL 2 instead of Hyper-V其他一路下一步。装完会提示重启重启之后 Docker Desktop 会自动启动托盘区出现小鲸鱼图标第一次启动可能要等一两分钟初始化。启动之后先别急着跑命令有几个设置值得改。打开 Docker Desktop 的设置界面从左到右依次看这几项。第一项是 General 里的 Start Docker Desktop when you sign in to your computer。如果你每天都在用勾上省事如果只是偶尔用建议取消勾选否则开机的时候它会占用启动资源拖慢整个系统。第二项是 Resources → WSL Integration。这里会列出你机器上所有 WSL 发行版把常用的那个开关打开。打开之后你在 WSL 终端里也能直接用 docker 命令不用再切回 PowerShell这个体验差别很大强烈建议开。第三项是 Resources 里的资源限制。WSL2 后端默认会占用最多一半的物理内存如果你机器只有 8G 内存容器一跑起来机器就会卡。可以在 Windows 用户目录下建一个.wslconfig文件来限制[wsl2] memory6GB processors4 swap2GB改完之后执行wsl --shutdownDocker Desktop 重新启动配置生效。这个文件的路径是C:\Users\你的用户名\.wslconfig注意是 Windows 用户目录不是 WSL 里的家目录很多人在这一步放错位置导致配置不生效。设置改完开一个 PowerShell 验证一下docker version docker run hello-world第一条能看到 Client 和 Server 两段版本信息说明客户端和后台守护进程都通了。第二条如果能打印出那段 Hello from Docker! 的欢迎文字说明整个链路已经完整跑通可以正式干活了。2.4 配置镜像加速与验证拉取速度默认情况下Docker 拉取镜像走的是境外仓库在国内网络环境下速度很不稳定有时候几十兆的镜像要拉半小时卡在 Pulling fs layer 半天不动。解决办法是配置国内镜像加速地址让拉取请求走更近的节点。配置入口在 Docker Desktop 的 Settings → Docker Engine这里是编辑 daemon.json 的界面。你会看到一段现成的 JSON在里面加一个registry-mirrors字段{ builder: { gc: { defaultKeepStorage: 20GB, enabled: true } }, experimental: false, registry-mirrors: [ https://your-mirror-address-1, https://your-mirror-address-2 ] }把地址换成你手上可用的加速源即可可以填多个Docker 会按顺序尝试。改完点右下角 Apply RestartDocker 会自动重启。验证是否生效用一个干净的方式拉取docker pull nginx:alpine docker info | findstr -i registrydocker info输出里如果有 Registry Mirrors 段并且列出了你配置的地址说明配置已经加载。我实测下来配好加速之后拉一个几十兆的基础镜像基本在十几秒内完成和之前动辄几分钟的等待完全不是一个量级。注意加速地址是会变化的如果某天突然拉不动了第一反应应该是换一个源而不是怀疑 Docker 坏了。另外首次拉取某个镜像之后后续都会命中本地缓存速度快得多所以别把每次docker run都当成在下载。3. 从零构建一个自己的镜像以 Python Web 服务为例3.1 镜像、容器和 Dockerfile 到底是什么关系在动手写之前有必要把这三个概念的关系理清楚因为后面所有的操作都建立在这个理解之上。我习惯用一个生活化的类比来说明镜像好比是一张光盘容器就是用这张光盘复制出来的一个正在运行的实例。镜像是一个只读的模板里面打包了操作系统的基础文件、运行环境、你的代码和启动命令。它是静态的、不会变的构建出来之后就固定了。容器则是镜像在运行时的具体表现它启动之后会在镜像之上加一层可写的层你对容器做的任何修改都只存在于这个容器里不会污染镜像本身。那 Dockerfile 是什么它是用来生成镜像的配方文件。里面一行行的指令描述的是以什么为基础、装什么依赖、复制什么文件、以什么命令启动。执行docker build的时候Docker 会按顺序读取这些指令一层一层地叠加最后产出一个可以直接运行的镜像。理解分层这个概念很关键因为这是 Docker 镜像体积和构建速度的核心。每一条 RUN、COPY、ADD 指令都会生成一层层与层之间可以复用。也就是说如果你只改了代码没改依赖重新构建的时候前面装依赖的那一层会直接命中缓存几秒钟就能完成。这也是为什么 Dockerfile 里指令的顺序不能随便写。一句话总结Dockerfile 是配方镜像按配方做出来的成品容器是成品的运行实例。三个概念各司其职理清楚了写 Dockerfile 就不会迷茫。3.2 编写 Dockerfile逐行拆解每一步的意图我们用一个最小的 Python Flask 服务来做例子目录结构是这样的myapp/ ├── app.py ├── requirements.txt ├── Dockerfile └── .dockerignoreapp.py 是一个最简单的 HTTP 服务from flask import Flask app Flask(__name__) app.route(/) def index(): return hello from my own image if __name__ __main__: app.run(host0.0.0.0, port8000)requirements.txt 里只有一行flask。重点看 DockerfileFROM python:3.11-slim WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt \ -i https://pypi.tuna.tsinghua.edu.cn/simple COPY . . EXPOSE 8000 CMD [python, app.py]现在逐行拆解这里面的设计意图因为每一行的顺序都是有讲究的。FROM python:3.11-slim选的是官方 Python 镜像的 slim 变体。为什么不选默认的完整版因为完整版镜像接近 1G而 slim 版本只有一百多兆去掉了大量编译工具和文档对运行一个 Flask 服务来说完全够用。镜像体积直接决定了后续拉取、部署、分发的速度能小则小。如果你的项目需要编译 C 扩展可能需要换回完整版或者 alpine 版本这是取舍。WORKDIR /app设定工作目录后续所有命令都相对于这个目录执行。如果不写这一行默认会落在根目录/一来不整洁二来容易出现权限问题。接下来这两行顺序是整个 Dockerfile 里最关键的COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt ...先复制依赖清单再安装依赖最后才复制业务代码。为什么要拆成两步而不是直接COPY . .然后安装因为 Docker 的层缓存是基于内容的。只要 requirements.txt 没变这一层就永远命中缓存。如果你反过来写先复制全部代码再装依赖那么你每次改一行 app.py都会导致缓存失效重新下载安装一遍所有依赖一个几十秒的构建会变成几分钟。--no-cache-dir这个参数也值得说它让 pip 不保留下载缓存的安装包能省下几十兆的体积。在容器场景里安装包下载缓存没有任何保留价值该丢就丢。COPY . .把当前目录所有文件复制进镜像的工作目录。注意它是复制到镜像里不是挂载你改了宿主机的代码镜像里的不会跟着变需要重新 build。这一点和后面的数据卷挂载是两回事别搞混。EXPOSE 8000是一个声明告诉使用者这个容器打算在 8000 端口提供服务。它本身不会真的开放端口真正映射端口是在docker run -p的时候。它的价值在于自文档化别人看你的 Dockerfile 就知道该映射哪个端口。CMD [python, app.py]是容器启动时执行的默认命令用 JSON 数组这种 exec 形式而不是 shell 字符串形式好处是 python 进程会成为容器里的 1 号进程能正确接收终止信号容器停止的时候更干净。3.3 构建镜像与参数详解文件准备好之后在 myapp 目录下执行构建docker build -t myapp:1.0 .这条命令里的每个部分都有含义。-t myapp:1.0是给镜像打标签冒号前面是名字后面是版本。强烈建议养成显式写版本号的习惯不要依赖默认的 latest。latest 在 Docker 里只是一个普通标签不是最新的意思用久了你会发现一堆镜像全是 latest完全分不清哪个是哪个。我自己的规范是本地实验用项目名:dev正式一点用项目名:日期或者语义化版本号。最后一个点号是构建上下文代表当前目录。这个点很关键Docker 构建的时候会把上下文目录的所有文件打包发给后台守护进程如果你的目录里有一个几 G 的数据文件或者 node_modules构建会慢得离谱。这就是.dockerignore存在的理由。在项目根目录新建.dockerignore.git __pycache__ *.pyc node_modules .venv *.log data/它的语法和 .gitignore 基本一致。加上之后构建速度会明显提升而且能避免把敏感文件打进镜像。构建过程中还有两个常用参数值得记住。--no-cache强制忽略所有缓存重新构建适合排查明明改了代码结果没生效这类玄学问题。--progressplain把构建日志的进度条模式切成纯文本模式构建出错的时候用这个参数才能看到完整的报错堆栈默认的折叠式输出经常把关键信息藏起来。构建完成后用这两条命令确认结果docker images docker history myapp:1.0前者列出所有镜像能看到 myapp 的大小和创建时间。后者展示这个镜像的每一层以及各层占用的大小是排查镜像为什么这么胖时的第一把工具。我见过太多人抱怨镜像几个 G一查 history 发现有一层装了一堆编译工具然后没删白白多出来几百兆。3.4 运行容器、端口映射与数据卷镜像建好跑起来看看docker run -d -p 8000:8000 --name myapp-run myapp:1.0-d是后台运行不加这个参数容器会占住当前终端。-p 8000:8000是端口映射格式是宿主机端口:容器端口前半部分你可以随意改比如-p 9000:8000那就是通过宿主机的 9000 端口访问。--name给容器起个名字方便后续用名字操作不然就得记那一长串随机 ID。跑起来之后验证docker ps docker logs -f myapp-rundocker ps能看到容器状态和端口映射情况docker logs看应用输出。如果容器启动后立刻退出docker ps里看不到它要用docker ps -a看所有容器包括已退出的然后用docker logs 容器名找原因。这个排查动作后面第 4 章会反复用到。浏览器访问http://localhost:8000能看到 hello from my own image 就说明整条链路打通了。接下来是开发时最常用的一个技巧代码挂载。前面说过COPY . .把代码打进镜像了改代码要重新 build。但日常开发阶段这样太慢可以用数据卷把宿主机的目录挂进容器docker run -d -p 8000:8000 \ -v ${PWD}:/app \ --name myapp-dev \ myapp:1.0PowerShell 里用${PWD}cmd 里用%cd%WSL 或 Git Bash 里用$(pwd)。挂载之后你在宿主机改 app.py容器里立刻就是新代码重启容器就能看到效果不用重新构建。生产环境当然还是老老实实把代码打进镜像这样才有一致性保证。提示在 Windows 上做目录挂载有个性能问题要注意。如果项目放在 C 盘、D 盘这类 Windows 文件系统下挂载进容器后的文件读写会明显变慢因为要经过一层跨文件系统的转换。如果项目涉及大量文件操作比如前端的热更新把代码放到 WSL 的 Linux 目录里比如\\wsl$\Ubuntu\home\user\myapp性能会好一个数量级。3.5 保存、导出与迁移镜像镜像做出来之后总会有需要搬家的场景换电脑、发给同事、丢到没有外网的机器上。这里有两组命令要分清用错了会出问题。第一组是save和load操作对象是镜像docker save -o myapp.tar myapp:1.0 docker load -i myapp.tarsave把镜像连同它的所有层和元数据打包成一个 tar 文件load在另一台机器上还原。还原出来的镜像名字和标签都保持原样是一条完整的迁移路径。文件可能比较大可以配合压缩工具先压一遍再传。第二组是export和import操作对象是容器docker export myapp-run -o myapp-fs.tar docker import myapp-fs.tar myapp:flat它导出的是容器的文件系统快照会丢掉镜像的层结构和一些元数据导入进去只有一个扁平的文件系统。体积可能更小但可追溯性差一般只在特殊场景下用。日常我推荐save和load。如果目标机器只是临时用一下其实更简单的做法是把 Dockerfile 和源码拷过去在那边重新docker build这样还能顺便验证 Dockerfile 的可复现性。真正需要 save 的场景是目标机器没有网络或者拉不到基础镜像。如果有私有仓库或者可以直接访问的镜像仓库那就是另一条路docker tag myapp:1.0 registry.example.com/team/myapp:1.0 docker push registry.example.com/team/myapp:1.0tag是给同一个镜像起一个带仓库地址的别名push推上去。这两条命令在团队协作里是标配但前提是你有一个能访问的仓库地址配置认证的部分这里就不展开了。4. 常见问题与排查实录踩过的坑都在这4.1 Virtualization support not detected 的完整排查链路这个报错出镜率极高我把完整的排查顺序整理出来照着走一遍基本都能定位。第一步看任务管理器 → 性能 → CPU → 虚拟化那一行。显示已禁用进 BIOS 打开前面 2.1 节讲过。这一步能解决大概六成的情况。第二步如果 BIOS 里明明开了任务管理器还是显示已禁用检查内核隔离。路径是 Windows 安全中心 → 设备安全性 → 内核隔离详细信息把内存完整性关掉重启。这个功能会和 WSL2 抢虚拟化资源冲突时 Docker 就是起不来。第三步检查 Windows 功能是否真的都开了。以管理员身份跑这段命令看状态Get-WindowsOptionalFeature -Online -FeatureName VirtualMachinePlatform, Microsoft-Windows-Subsystem-Linux, HypervisorPlatform三个功能的 State 都应该是 Enabled。有哪个是 Disabled用Enable-WindowsOptionalFeature打开并重启。第四步如果你是在虚拟机里套娃装 Docker需要开启嵌套虚拟化这个前面提过属于宿主机的虚拟机软件设置不在 Windows 内部。第五步看 Docker Desktop 自己的日志。托盘图标右键 → Troubleshoot → 打开日志目录找com.docker.backend.exe相关的日志文件搜 virtualization报错最原始的原因一般都在那里。微软社区和各类技术社区里搜到的解决方案最后基本都指向这几个方向但日志能帮你确认到底是哪一环。4.2 WSL2 内核报错与 0x80370102 的处理WSL 2 installation is incomplete或者0x80370102这类报错八成是 WSL 内核版本太旧或者安装不完整。处理方式很简单跑一遍wsl --update wsl --shutdown然后重启 Docker Desktop。如果wsl --update因为网络原因卡住去官方文档页面下载内核更新包手动安装效果完全一样。还有一种情况是 WSL 发行版本身出了问题比如wsl -l -v能列出名字但 STATE 一直是 Installing 或者 Stopped 起不来。可以在 PowerShell 里单独启动它看看报错wsl -d Ubuntu如果报的是文件系统相关的错误可以尝试注销重装发行版注意这会清空该发行版里的数据wsl --unregister Ubuntu wsl --install -d Ubuntu这个操作不可逆执行前务必确认里面没有重要数据或者先wsl --export备份一份。4.3 镜像拉取慢、卡在 pulling fs layer这个问题前面提过加速配置但实际遇到的时候还是有几个细节值得补充。首先要区分是慢还是卡死。如果进度条在缓慢推进哪怕很慢那只是网络问题配加速源能解决。如果十几分钟停在同一个 layer 完全不动可能是那个源失效了或者网络中断先 CtrlC 中断换个源再试。第二个排查点是磁盘空间。我遇到过 c 盘只剩两三个 G 的时候拉取镜像会静默失败或者卡住因为 WSL2 的虚拟磁盘写不进去了。用docker system df看 Docker 占用情况再看看 Windows 系统盘的剩余空间。第三个技巧是分层拉取加超时。在 daemon.json 里可以加超时配置但更实用的做法是分步拉比如先拉一个小体积的核心镜像确认网络通畅再拉大的。docker pull alpine:latest docker pull python:3.11-slim最后还有一个常见误区把docker run当成拉取。docker run nginx会先检查本地有没有没有才去拉但它是静默拉取的没有任何进度提示看起来就像卡住了。所以建议养成先docker pull再docker run的习惯至少知道自己卡在哪一步。4.4 容器起来就退出、端口占用的排查方法容器启动后docker ps找不到它说明它已经退出了。第一步永远是看日志docker ps -a docker logs 容器名八成能看到一段 Python 的 traceback 或者类似的错误信息。常见原因有几个应用绑定了 127.0.0.1 而不是 0.0.0.0导致容器外部访问不到依赖没装全启动就崩配置文件路径不对代码里写的是相对路径但 WORKDIR 设的目录不对。bind to 127.0.0.1这个问题值得单独强调。容器有自己的网络命名空间127.0.0.1 在容器里指的是容器自己。如果你的服务只监听 127.0.0.1那从宿主机是绝对访问不到的。Flask 的app.run()默认只绑 127.0.0.1必须显式写host0.0.0.0。这个坑我踩过不止一次排查半天以为是端口映射写错了。端口占用是另一类问题报错信息通常很明确Error starting userland proxy: listen tcp4 0.0.0.0:8000: bind: address already in use意思是宿主机的 8000 端口已经被别的程序占了。查是谁占的netstat -ano | findstr :8000 tasklist | findstr 上面查到的PID要么停掉那个程序要么换个宿主机端口比如-p 8080:8000。我个人的习惯是宿主机的端口统一往后挪一位或者换个段避免和常用的开发端口冲突。4.5 磁盘被吃掉的清理姿势Docker 用久了 C 盘越来越小这是所有人都会遇到的问题。原因是每个镜像、每个容器、每次构建的中间层都会占用空间而且默认不会自动回收。先看占用分布docker system df docker system df -v第一条给出镜像、容器、本地卷、构建缓存四大类的总占用。第二条更详细列出每个镜像和容器的具体大小方便你定位到底是哪个大块头在占地方。清理要分级进行不要上来就梭哈。最温和的是只清理悬空资源docker image prune docker container prune悬空镜像指的是那些没有标签、不被任何容器引用的中间层清掉它们几乎没有副作用。再进一步是清理构建缓存docker builder prune这个特别有用多阶段构建和频繁 rebuild 会攒下大量缓存层我见过一台上线用的机器里构建缓存占了三十多个 G。最彻底的是全清docker system prune -a --volumes-a会把所有没有被运行中容器使用的镜像都删掉包括你精心构建的那个 myapp。--volumes会删除未被使用的数据卷如果你有容器依赖某个卷里的数据删了就找不回来了。执行前一定用docker ps -a和docker volume ls确认一遍。还有一个容易被忽略的点prune清掉的是 Docker 层面的空间但 WSL2 那个虚拟磁盘文件不会自动缩小。也就是说docker system df显示只占了 5G但 C 盘上那个 vhdx 文件可能还是 40G。要真正释放得让 WSL 收缩磁盘wsl --shutdown diskpart进入 diskpart 交互界面后select vdisk fileC:\Users\你的用户名\AppData\Local\Docker\wsl\data\ext4.vhdx attach vdisk readonly compact vdisk detach vdisk exit路径以你机器上实际的位置为准不同版本可能不同。这一步做完磁盘空间才会真正回到 Windows。下面这张表是我整理的高频问题速查遇到问题可以先在这里对一遍现象最可能的原因处理方式装完启动报 virtualization support not detectedBIOS 虚拟化未开或内核隔离冲突开 BIOS 虚拟化关内存完整性WSL 2 installation is incompleteWSL 内核过旧wsl --update后重启拉镜像卡在 pulling fs layer未配加速源或磁盘空间不足配 registry-mirrors清磁盘容器启动后立刻退出应用报错或绑定 127.0.0.1看docker logs改0.0.0.0端口映射无效宿主机端口被占用netstat查占用换端口C 盘空间莫名减少镜像、缓存、vhdx 膨胀docker system prune compact vdisk改代码后容器没变化代码是 COPY 进镜像的重新 build 或改用卷挂载5. 让镜像更小更快多阶段构建和几条压箱底的经验5.1 多阶段构建的完整实操当你需要编译型语言的时候镜像体积会迅速失控。比如一个 Go 服务如果直接在 golang 完整镜像里构建并运行镜像轻松超过 800M因为里面塞满了编译器、SDK 和一堆根本用不到的依赖。多阶段构建就是解决这个问题的标准方案。它的核心思路是在一个 Dockerfile 里写多个 FROM 阶段前面的阶段负责编译后面的阶段只把编译产物拷过来中间那些工具链全部丢弃。看一个完整的 Go 例子FROM golang:1.22-alpine AS builder WORKDIR /src COPY go.mod go.sum ./ RUN go mod download COPY . . RUN CGO_ENABLED0 GOOSlinux go build \ -ldflags-s -w \ -o /out/app . FROM alpine:3.19 RUN apk add --no-cache ca-certificates tzdata WORKDIR /app COPY --frombuilder /out/app /app/app EXPOSE 8080 CMD [/app/app]拆解几个关键点。第一个阶段用 golang:1.22-alpine 完整环境编译go mod download单独成层是为了让依赖缓存生效。CGO_ENABLED0是关掉 C 语言的链接依赖这样编出来的二进制可以在任何 Linux 环境里跑不依赖宿主机的 glibc 版本是静态编译的关键。-ldflags-s -w去掉符号表和调试信息能再瘦身 20% 到 30%。第二个阶段从 alpine 开始只做三件事装证书和时区数据、把编译好的二进制拷过来、设置启动命令。COPY --frombuilder这个语法就是多阶段构建的核心它明确告诉 Docker 从哪个阶段取文件。这么一套下来最终镜像从 800M 压到 20M 以内是常态。体积小了之后拉取快、启动快、镜像仓库存储成本低好处是全方位的。如果你连 alpine 的几十兆都嫌大还有 scratch 这个终极选择。scratch 是一个完全空的镜像什么基础环境都没有只能跑静态编译的二进制。代价是没有 shelldocker exec进去连命令行都没有排查问题会很痛苦。所以除非是追求极限体积的场景否则 alpine 的平衡点更合适。5.2 层缓存与构建加速的实战技巧理解了层缓存的原理就能做出很多优化。除了前面说的把依赖安装和代码复制的顺序分开之外还有几个我常用的技巧。第一个是合并 RUN 指令。每一条 RUN 都会生成一层而层是有开销的。把相关的命令用串起来并在同一个 RUN 里清理临时文件能显著减小体积RUN apt-get update \ apt-get install -y --no-install-recommends curl ca-certificates \ rm -rf /var/lib/apt/lists/*注意这里必须合成一条如果你拆成三条前一条装的包在后面的层里并没有真正被删除只是被覆盖了镜像大小照样涨。这就是分层机制带来的一个反直觉的地方。第二个是--no-install-recommends这个参数它会阻止 apt 装推荐但不必要的依赖包能把安装体积砍掉一大半。pip 那边对应的就是--no-cache-dir。第三个是构建缓存挂载。BuildKit 提供了--mounttypecache语法可以让包管理器的缓存跨构建复用同时不进入最终镜像RUN --mounttypecache,target/root/.cache/pip \ pip install -r requirements.txt这样每次构建都能复用上次下载的包缓存构建速度提升非常明显。这是我最近两年最喜欢的 Dockerfile 特性之一尤其是依赖多的项目效果立竿见影。第四个是并行构建。多阶段构建的各个阶段如果有依赖关系Docker 会自动并行处理能并行的部分。前提是用 BuildKitDocker Desktop 默认已经启用了。你可以在设置里确认或者构建时加DOCKER_BUILDKIT1环境变量。5.3 长期使用中积累的几条经验用 Docker Desktop 这两年有些经验是文档里不会写、但日常工作和排查全靠它们的。关于镜像命名我吃过亏。早期图省事全用 latest结果本地攒了十几个不同项目的 latestdocker images里一片混乱谁也认不出哪个是哪个最后只能全删重来。现在的做法是严格用项目名:版本或者项目名:日期本地实验加-dev后缀一眼就能分辨。这个习惯看起来是小事实际上决定了你后期能不能管得住本地环境。关于资源限制不要迷信默认配置。WSL2 默认会占用一半物理内存如果你机器有 32G 还好8G 的机器上跑几个容器就会开始卡。.wslconfig里的 memory 和 processors 一定要按实际情况设宁可设小一点需要的时候再调大也别让它把系统拖死。我一般给 WSL2 分 6 到 8G 内存日常够用。关于 WSL 和 Windows 之间的文件位置这是个影响开发体验的关键决策。项目代码放在/home/user/xxx这种 Linux 路径下容器挂载读写速度正常放在/mnt/c/Users/...这种 Windows 路径下跨文件系统访问会慢很多前端项目做热更新的时候尤其明显改一行代码等十几秒是常事。所以我的做法是代码仓库放 WSL 里面用 VS Code 的 WSL 远程模式打开编译、容器、终端全在 Linux 侧Windows 只负责开个浏览器。关于数据安全Docker 的默认行为是删了就没了。容器删掉里面没挂出来的数据就一起没了数据卷删掉里面的数据库文件也没了。所以任何重要的状态比如数据库文件、上传目录、配置全都要用卷或者绑定挂载放到宿主机。我见过有人在一个容器里跑 MySQL 存了几天测试数据一个docker system prune -a --volumes下去全没了回头找都没处找。最后说一个小的效率习惯把常用的命令写成脚本或者别名。比如清理悬空资源的、一键重建容器并跟日志的、导出镜像的写成.ps1或者.sh脚本放在项目根目录。日积月累下来这些脚本能省掉大量的重复输入也避免每次凭记忆敲命令敲错参数。这些都是在真实项目里踩出来的不是什么高深技巧但确实能让你少走很多弯路。
返回列表