Docker in Docker实战指南:原理、玩法与避坑全解析
最近总有人问 Docker in Docker 怎么玩缩写 DinD听起来像套娃但它是真实场景里经常用得上的技巧。热词里那堆问题什么 Docker Desktop 装不上、docker 命令报 permission denied、镜像拉取龟速全是基础没收拾利索导致的而真正想在容器里再跑一套 Docker 做隔离实验的人往往又会卡在内层 daemon 起不来、端口映射不通、磁盘莫名其妙爆满这些地方。这篇文章不打算堆概念就用一线实操的角度把 DinD 的原理、玩法、坑全部讲透无论你是跑 CI 的、做依赖隔离的还是只想搭一套干净测试环境的人看完都能直接上手。1. 先搞清楚容器里的 Docker 是怎么“套娃”的1.1 两种常见玩法DinD 和 DoD先说概念。Docker in Docker简单说就是在容器里再跑一个完整的 Docker 守护进程dockerd。这个守护进程跟宿主机上的 Docker daemon 完全独立它有自己的镜像仓库、自己的网络栈、自己的容器列表你在里面 docker pull、docker run操作的都是这个“第二层”环境。这种模式通常缩写为 DinD。很多人容易把另一招混进来就是直接把宿主机的 /var/run/docker.sock 挂进容器这种方式常被称为 DoDDocker outside of Docker。容器里的 docker 命令根本不是在本机起 daemon而是通过 socket 去调用宿主机的 daemon。两者看起来都在“容器里用 Docker”但隔离边界完全不同。对比项DinD容器内全量 daemonDoD共享宿主机 socket独立性完全独立镜像、容器、网络互不干扰与宿主机共享看到的同一个 daemon所需权限一般需要 privileged 特权模式只需要挂载 socket零特权也可以安全边界隔离性较好但 privileged 本身有风险相当于把宿主机 Docker 管理权交给容器风险较高典型场景CI 隔离构建、实验环境、模拟多套环境面板类容器、定时任务里偶尔调用 docker 命令性能与磁盘独立存储驱动可能有额外开销复用宿主机存储开销小1.2 为什么不能在普通容器里直接执行 docker 命令先解释一个新手特别容易困惑的点装好 Docker 后在普通容器里执行 docker ps大概率直接报 permission denied while trying to connect to the docker api at unix:///var/run/docker.sock。这不是玄学原因就三条第一容器里往往根本没有 dockerd 进程。Docker 命令本质上是一个客户端它要通过 /var/run/docker.sock 去连接 daemon普通容器里这个路径上什么都没有。第二就算你在容器里把 dockerd 装上了默认的容器隔离也不允许它正常工作。dockerd 需要挂载文件系统、操作 iptables、创建虚拟网络设备、管理 cgroup这些都需要 CAP_SYS_ADMIN 等一系列内核能力普通容器默认把这些能力全部收走了。第三如果直接把宿主机的 socket 挂进去那就不是“容器内跑 Docker”而是变成了“容器内操作宿主的 Docker”也就是上面说的 DoD。这倒能解决命令不存在的问题但你得清楚权限边界也跟着出去了。所以想要真正在容器里跑一套 Docker 出来DinD 的“特权模式”几乎绕不开。privileged 相当于把宿主机的大部分内核能力交给容器配合挂载一些系统目录dockerd 才可能在容器内起来。1.3 什么场景真的需要 DinD我自己的判断标准很简单只有当“内层环境必须独立、可丢弃、受控”的时候DinD 才有价值。举例来说CI 流水线里每个构建任务都应该跑在一个干净的 Docker 环境中任务结束后整个环境销毁避免上一次构建的残留影响下一次。这种场景里 DinD 非常趁手。再比如你不想在宿主机上弄乱环境想同时测试 MySQL 8.0、Redis 主从、安全靶场镜像甚至是 Hadoop 这样的重型集群把它们全部装进一个 DinD 沙箱里测完直接删掉外层容器和 volume宿主机干干净净。这种需求里宿主环境越干净DinD 的价值越大。反过来如果你的需求只是在某个应用容器里偶尔调用几条 docker 命令比如在功能复杂的管理面板里操作 docker compose 项目那通常用 DoD 挂个 socket 就行完全没必要再折腾一个特权容器出来。我后面会反复强调这一点能用轻量方案解决就不要把事情搞复杂。2. 开工之前把宿主机 Docker 环境收拾利索2.1 Docker 安装与 Docker Desktop 的虚拟化坑DinD 是基于宿主机 Docker 的第一步永远是保证宿主机 Docker 能用。Linux 上的安装很简单Ubuntu/Debian 系直接sudo apt update sudo apt install -y docker.io docker-compose-v2 sudo systemctl enable --now docker如果你是从官方仓库安装的需要先添加 GPG key这里不展开。装完后建议顺手把当前用户加进 docker 组避免每次都要 sudosudo usermod -aG docker $USER newgrp dockerWindows 上就麻烦一些。Docker Desktop 依赖 WSL2 和虚拟化功能最常见的报错就是 virtualization support not detected或者 Docker Desktop failed to start because virtualisation support wasnt detected。这个报错一般不是 Docker 本身的问题而是底层虚拟化没就绪。排查顺序去“启用或关闭 Windows 功能”里确认“虚拟机平台”和“适用于 Linux 的 Windows 子系统”两项已勾选再进 BIOS 确认 VT-xIntel或 AMD-V 已打开最后管理员执行 wsl --status确认 WSL 版本是 2。通常这三步做完Docker Desktop 就能正常启动了。顺便提一句Windows 上用 Docker 比较高效的方式是直接用 WSL2 里的 Docker Engine而不是桌面版里的 Linux 容器虚拟化。如果你只是要一个跑 DinD 的宿主环境WSL2 里装 Docker 再搭配一块独立磁盘比桌面版省内存也更接近生产环境的 Linux 操作习惯。2.2 镜像拉不动怎么办加速器基本操作镜像下载慢是另一个高频痛点。Docker Hub 在国内访问经常超时尤其 dind 镜像本身比较大卡在 pull 阶段很影响心情。通用做法是给 Docker 配置 registry mirror。新建或编辑 /etc/docker/daemon.jsonsudo mkdir -p /etc/docker cat EOF /etc/docker/daemon.json { registry-mirrors: [https://xxxx.mirror.aliyuncs.com] } EOF sudo systemctl daemon-reload sudo systemctl restart docker注意两个坑一是 xxxx 部分是每家云厂商分配给个人的专属地址不要照抄别人的自己去控制台拿二是加速器地址可能会调整配好之后检查一下 docker info 里的 Registry Mirrors 是否生效。别配完不重启那等于白改。另外构建镜像后需要 push 到自己私有仓库的场景加速器只解决拉取源的问题推送还是要走你配置的 Registry 地址。2.3 权限报错的本质permission denied 排查热词里那句 permission denied while trying to connect to the docker api at unix:///var/run/docker.sock 出现频率很高这多半不是 DinD 特有的问题而是基础 Docker 环境没收拾好。快速定位就三步第一步systemctl status docker 看 daemon 是否真的在运行。经常有人装完 Docker 却忘了启动报出的错一模一样。第二步id 你的用户名 看是否在 docker 组里。如果不在执行 sudo usermod -aG docker $USER然后重新登录终端。注意改完组要新开一个 shell 才生效旧 shell 不会自动更新组身份。第三步检查是不是环境变量 DOCKER_HOST 被设置到了某个失效地址。比如你之前 export 过 DOCKER_HOSTtcp://某个不存在的主机客户端会忽略本地 socket 直接去连那个地址报出来的错误也可能是 permission denied 或 connection refused但问题根本不在权限。可以肯定地告诉你不要为了省事去 chmod 777 /var/run/docker.sock那等于把 Docker 大门敞开任何容器的 root 用户都能接管整个宿主机。正确做法永远是加组、启服务、检查并清理环境变量。3. 核心实战用 docker:dind 跑通第一个 DinD 容器3.1 最简单的启动命令与参数拆解先来一个最小化的 DinD 实例。在宿主机上执行docker run -d --privileged --name dind \ -e DOCKER_TLS_CERTDIR \ -p 127.0.0.1:2375:2375 \ docker:dind这里面的每个参数都值得说清楚。--privileged 是关键。dockerd 在容器里要创建 overlay 文件系统、配置 iptables、管理 cgroup普通容器能力不够必须靠特权模式放开内核能力。你可以在无特权模式下试一次大概率看到容器疯狂重启日志里一堆 operation not permitted。-e DOCKER_TLS_CERTDIR 是关闭 TLS。docker:dind 镜像默认会在容器内自动生成一套 TLS 证书并让 daemon 以 2376 端口、强制 TLS 方式对外服务。这样做很安全但对首次上手不友好因为宿主机客户端也要跟着配证书。这里的命令把 TLS 关掉daemon 就会用非加密的 2375 端口监听方便本机调试。-p 127.0.0.1:2375:2375 是端口映射把宿主机的回环地址映射到容器内 2375。注意我只绑定了 127.0.0.1这一点非常重要。2375 是非加密端口如果直接映射到 0.0.0.0 或者宿主机公网 IP相当于把一台 Docker daemon 裸奔在网络上。跑完检查一下docker ps docker exec dind docker version curl http://127.0.0.1:2375/version三条命令分别确认外层容器、容器内 docker 命令、宿主机到内层 daemon 的连接都正常。curl 能返回一长串 JSON说明 DinD 已经活了。3.2 从宿主机管理“容器内的 Docker”DinD 跑起来之后最直观的用法就是像操作普通 daemon 一样通过 DOCKER_HOST 去调它。我演示一下export DOCKER_HOSTtcp://127.0.0.1:2375 docker ps docker pull nginx:alpine docker run -d --name inner-nginx -p 8080:80 nginx:alpine docker ps注意export DOCKER_HOST 之后你宿主机上的 docker 命令就会全部变成“内层 daemon”的客户端。此时 docker ps 看到的不是宿主机原来的容器列表而是 DinD 里面的容器列表。这会让人产生“哎我宿主容器怎么不见了”的错觉第一次用的人特别容易懵。如果你不想改全局环境变量也可以用 -H 参数单条指定docker -H tcp://127.0.0.1:2375 ps用完记得 unset DOCKER_HOST否则后续在宿主机执行 docker 命令都可能操作到内层 daemon万一误删了内层容器或者清了镜像后悔都来不及。这一点我踩过后面还会再说一个更隐蔽的坑。在这个模式下外层 DinD 容器像是你“租”了一个独立 Docker 环境随时可以销毁它来重置环境。内层容器、内层镜像只存在于 DinD 的存储层里不会污染宿主机原生的 Docker。3.3 让 DinD 里的数据活下来持久化与存储驱动默认情况下DinD 里的所有数据都写在容器可写层里容器一删数据跟着灰飞烟灭。很多场景需要保留内层数据比如你测试 MySQL 时灌了几天的数据不希望重建 DinD 后全没了。最简单的方式是给 /var/lib/docker 挂一个 volumedocker volume create dind-data docker run -d --privileged --name dind \ -e DOCKER_TLS_CERTDIR \ -p 127.0.0.1:2375:2375 \ -v dind-data:/var/lib/docker \ docker:dind这里有一个经常被问到的问题能不能直接把宿主机的 /var/lib/docker 挂进 DinD我的答案很明确别这么干。两个 daemon 同时写同一个 Docker 数据目录轻则镜像互相干扰重则整个数据目录损坏。要用 volume就单独创建一块卷不要让内外共用。存储驱动方面DinD 常用的是 vfs 和 overlay2 两种。vfs 最稳几乎在所有内核上都能跑但性能最差而且每个镜像层都完整复制一份非常占空间。overlay2 性能好但需要容器内能挂载 overlayfs如果内核模块没加载或者权限不够dockerd 会直接起不来。如果启动 dind 时想指定 overlay2可以在镜像名后面追加参数docker run -d --privileged --name dind \ -e DOCKER_TLS_CERTDIR \ docker:dind --storage-driveroverlay2启动后可以用 docker exec dind docker info 确认当前存储驱动。如果容器反复重启或者日志里出现 mount 相关报错果断退回 vfsdocker run -d --privileged --name dind \ -e DOCKER_TLS_CERTDIR \ docker:dind --storage-drivervfs实验和 CI 里我比较推荐直接用 vfs反正 DinD 本身就是临时环境别为了性能给自己添堵。生产级持久化场景已经超出 DinD 的范畴了这个工具的定位就是轻量、隔离、可丢弃。4. 实战场景扩展从 CI 到依赖管理的几种典型玩法4.1 用 DinD 做完全隔离的 CI 构建环境GitLab Runner 为例CI 里的 DinD 是我见过最标准的用法。GitLab Runner 的 Docker executor 会在每个 job 里创建两个容器一个是执行脚本的 job 容器另一个是 docker:dind 服务容器。job 容器需要 docker 命令时通过 DOCKER_HOST 连到 dind 服务这样每个 job 都有一台全新的 Docker daemon构建互不污染跑完整个环境自动销毁。一个最简的 .gitlab-ci.yml 长这样build-job: stage: build image: docker:24.0.7 services: - name: docker:24.0.7-dind alias: docker variables: DOCKER_HOST: tcp://docker:2375 DOCKER_TLS_CERTDIR: IMAGE_NAME: demo:latest script: - docker version - docker build -t $IMAGE_NAME . - docker push registry.example.com/$IMAGE_NAME这里的关键是把 DOCKER_HOST 指向别名 docker 的 dind 服务容器。services 里的 docker:dind 会在构建网络里注册一个叫 docker 的主机名job 容器里直接 tcp://docker:2375 就能连上它。我在实际配置中踩过一个细节runner 本身跑在宿主机时要给 runner 的 config.toml 加上 privileged true否则 dind 服务容器无法获得特权GitLab 官方文档也建议在 Docker executor 里默认开启 privileged。如果不开job 里的 docker build 会反复失败报错却千奇百怪很容易被误导到别的地方去。4.2 用 DoD 轻量解决“容器内需要 Docker”的日常需求不是所有“容器里想用 docker”的需求都值得上 DinD。如果你的容器只是偶尔执行几条 docker 命令挂一个 socket 就够了。docker run --rm -it \ -v /var/run/docker.sock:/var/run/docker.sock \ docker:24.0.7 sh进入容器后 docker ps看到的就是宿主机的容器列表。这个方案轻量、快、几乎不占额外资源适合面板类容器、运维工具容器、定时任务容器这类场景。很多人在管理青龙这类依赖复杂的容器时想从容器里调度外部的 docker 服务走的就是这条路在 compose 文件里把 socket 挂进去容器内部执行 docker 命令就能操作宿主。但安全性一定要讲透把 /var/run/docker.sock 挂进容器等于把宿主机 Docker 的管理权完全交给这个容器。容器里的进程如果被攻破攻击者直接 docker run -v /:/host 一个特权容器就能拿到宿主机整个文件系统。所以 DoD 只推荐在受信任的、受控的容器里使用例如你自己的运维工具容器。如果容器面向外部输入或者会运行不可信脚本千万别挂 socket。4.3 更高级在 DinD 里编排多套服务MySQL 主从、Redis 集群、靶场、Hadoop 实验DinD 其实是一个很好的“实验室收纳箱”。比如你的工作要经常验证数据库主从、缓存集群或者安全靶场而这些服务又不想永久留在宿主机环境里那就可以把它们全部装进一个 DinD 容器中。操作方式很简单。假定 dind 容器正在运行直接用内层 daemon 去拉镜像和启动服务export DOCKER_HOSTtcp://127.0.0.1:2375 docker run -d --name mysql8 -e MYSQL_ROOT_PASSWORDroot123 mysql:8.0 docker run -d --name redis-master redis:7 docker run -d --name redis-slave --link redis-master:master redis:7这套内层环境完全隔离在 DinD 里。测完想重置一句 docker rm -f dind里面的 MySQL、Redis 连带所有数据层全部清掉宿主机本身不会残留一堆名字带 mysql、redis 的容器。DVWA 这类靶场镜像同样适合这么干。在 DinD 里跑一个 DVWA 做安全教学实验宿主机的网络和文件系统不会被靶场里的漏洞服务波及。容器里的 Hadoop 集群实验也是这个思路镜像动辄几个 GB在 DinD 里跑完就删宿主机能保持干净。需要注意的是DinD 容器对外的端口映射机制比较特殊。内层 daemon 创建的端口映射实际绑定在 DinD 容器的网络命名空间里。也就是说你在内层 docker run -p 8080:80 nginx宿主机并不能直接用 localhost:8080 访问而是要先拿到 DinD 容器的 IP用 http://dind容器IP:8080 访问。想从宿主机客户端连 DinD 里的 MySQL也是同样的逻辑主机填 DinD 容器 IP端口填内层 -p 指定的端口密码就用内层 MySQL 的 root 密码。访问方式适用场合操作要点通过 DOCKER_HOST 管理内层 daemon日常调试、CI 内部export DOCKER_HOSTtcp://127.0.0.1:2375通过 DinD 容器 IP 访问内层服务Linux 宿主机单个服务docker inspect dind 取 IPDocker DesktopmacOS / Windows先映射外层端口再通过 localhost 访问--network host仅 Linux内层容器端口直接打在宿主网络栈上如果喜欢用 compose 管理服务也可以直接在 DinD 容器里执行 docker compose前提是 dind 镜像里包含 compose 插件或者你挂一个二进制进去。更常见的做法是在宿主机上写好 compose 文件定义一个 dind 服务services: dind: image: docker:dind privileged: true environment: DOCKER_TLS_CERTDIR: ports: - 127.0.0.1:2375:2375 volumes: - dind-data:/var/lib/docker volumes: dind-data:然后用 docker compose up -d 启动就能得到一个标准的 DinD 实验环境。后续想在内层跑服务继续用 DOCKER_HOST 连进去编排即可。4.4 构建镜像的另一个思路DinD vs kaniko另一个容易和 DinD 混淆的需求是“在容器里构建 Docker 镜像”。DinD 能构建但代价是要一个特权容器。如果你所在环境不允许 privileged比如安全性要求较高的 Kubernetes 里可以考虑 Kaniko。Kaniko 的原理和 DinD 完全不同它不启动任何 daemon而是在用户态直接解析 Dockerfile 并构建镜像层所以不需要特权模式。一个很简单的用法是这样docker run --rm \ -v $(pwd):/workspace \ gcr.io/kaniko-project/executor:latest \ --context/workspace \ --destinationmyregistry/myimage:latest它把当前目录当作构建上下文最终把镜像推到目标仓库。跟 DinD 相比Kaniko 破坏面小适合受限环境但调试体验不如 DinD 直观毕竟少了完整的 daemon 和容器运行时。构建镜像时如果只是本地测试DinD 顺手如果要往 K8s 环境里塞一个镜像构建器优先看 Kaniko 或者 buildkit 的 remote builder。5. 踩坑实录DinD 实战中的典型问题与排查5.1 daemon 起不来存储驱动、cgroup 与特权模式DinD 最常见的症状是外层容器反复重启。先不要瞎猜第一步永远是 docker logs dind 看 daemon 日志。我见到最多的报错是 Error creating topological map 后面跟着 operation not permitted或者 failed to mount overlay。这类问题十有八九是存储驱动或权限。处理顺序先确认启动命令带了 --privileged其次尝试显式指定 --storage-driveroverlay2如果还不行就退回 vfs。另一个容易被忽略的是 cgroup。在有 systemd 的宿主上DinD 的 daemon 如果无法访问 cgroup 树启动日志里会出现 cgroup 相关错误。解决办法有两个方向一是启动 dind 时挂载宿主的 /sys/fs/cgroup二是更保险的在 DinD 容器内部使用 vfs 并加严格的资源限制。实际项目中我发现大部分情况下不是 cgroup 版本问题而是宿主本身跑在虚拟机或容器里虚拟化层没有正确暴露 cgroup 信息。这种环境里直接上 vfs 通常能绕开一大堆麻烦。另外如果你计划在 DinD 里跑 GPU 容器比如需要 nvidia-container-toolkit 的场景光有 privileged 远远不够还需要让外层容器挂载 nvidia 的设备节点并配合 toolkit 配置。嵌套环境下这块比较复杂没有特殊需求不建议在 DinD 里碰 GPU用 DoD 或者宿主原生运行反而更稳。5.2 内层容器网络不通、端口映射失效内层容器间网络不通或者端口映射从宿主机访问不到是最让人抓狂的问题。先说端口映射我在前面已经提到内层的 -p 映射实际只发布在 DinD 容器的 IP 上。你从宿主机访问 localhost:8080 失败是正常的访问 DinD 容器 IP 的对应端口才能进入。如果你在 DinD 里发现内层容器之间无法互 ping多半是 dind 容器内的 iptables 规则没生效。一个粗暴但有效的办法是让外层 dind 以 --network host 模式启动。这样内层 daemon 创建容器时网络栈直接挂在宿主网络栈上端口和互通都简单很多。代价是 DinD 容器与宿主完全共享网络命名空间隔离性下降而且 Docker Desktop 并不支持 host 网络模式。还有一种情况是外层宿主启用了 firewalld 或 ufw拦截了 DinD 端口映射的流量。排查时先临时放行或关闭防火墙试试放行后通了再细加规则。DinD 本身已经够复杂了别让防火墙问题在最后一环再搅一次局。5.3 磁盘膨胀DinD 常见的“吃满磁盘”问题DinD 环境用久了磁盘会以一种很隐蔽的方式被吃满。内层 daemon 的镜像、容器层、构建缓存全部堆在 DinD 的存储目录里外层 docker df 却看不到这些只显示 DinD 容器可写层增长。你明明清理了宿主机镜像磁盘还是越来越少大概率就是 DinD 里的数据在膨胀。清理方法不复杂。假设已经用 DOCKER_HOST 连到内层 daemondocker system df docker image prune -f docker builder prune -f docker container prune -f如果是纯实验环境更高效的办法是删掉整个 dind 容器和 volume 重建docker rm -f dind docker volume rm dind-data我个人的习惯是给内层 daemon 加上日志轮转和存储配额防止它在长跑项目里默默写爆磁盘。启动 dind 时加上docker run -d --privileged --name dind \ --storage-opt size20g \ --log-opt max-size10m --log-opt max-file3 \ -e DOCKER_TLS_CERTDIR \ docker:dind--storage-opt size 在 vfs 或 devicemapper 下有效overlay2 模式下不一定生效但日志参数总是有用的。对于 CI 里的 DinD任务结束自动销毁一般不会积累反而是本地常驻的实验型 DinD 要盯紧磁盘。5.4 连接失败docker api permission denied 与 TLS 证书最后一个高频问题是从外部连 DinD 时报 permission denied 或 connection refused。报错里如果带了 unix:///var/run/docker.sock说明客户端根本没有使用 DOCKER_HOST 变量还在找本机 socket。检查一遍环境变量确认当前 shell 真的 export 了 DOCKER_HOSTtcp://127.0.0.1:2375或者用 docker -H 显式指定。如果报 connection refused一般是端口没映射对。DinD 容器没做 -p 映射或者映射的宿主端口不是 2375都会导致这个结果。直接在宿主机 curl 一下curl http://127.0.0.1:2375/version如果 curl 通而 docker 命令不通检查 docker 客户端版本是不是太老老版本可能和 daemon 的 API 版本不兼容。如果你开启了 TLS也就是没有设置 DOCKER_TLS_CERTDIR那 daemon 只监听 2376 端口而且强制要求客户端带证书。这时候宿主机要能正常连接需要把证书目录挂出来docker run -d --privileged --name dind \ -e DOCKER_TLS_CERTDIR/certs \ -v /tmp/dind-certs:/certs \ -p 127.0.0.1:2376:2376 \ docker:dind宿主机这边配置export DOCKER_HOSTtcp://127.0.0.1:2376 export DOCKER_TLS_VERIFY1 export DOCKER_CERT_PATH/tmp/dind-certs/clientTLS 在本地调试时确实烦人但只要 daemon 会暴露到网络上这套就值得配。早期我偷懒关掉 TLS结果一台测试机的 2375 端口被扫到整个 daemon 被拿去挖矿。后来所有暴露端口一律走 TLS这是经验之谈别省这一步。6. 最后再唠叨几句个人经验这几招用下来我对 DinD 的总体判断是它是一个特别适合“实验”和“CI”的工具但不适合长期承载核心业务。privileged 模式的破坏力摆在那里能不用就不用能用 DoD 替代就尽量别开 DinD。我在实际使用中最推荐的组合是CI 里用官方 docker:dind 做隔离构建本地实验用 Docker socket 的 DoD 搞定大多数依赖问题只有真正需要完整独立环境的时候才启动 DinD并且给每个 DinD 挂独立的 volume 和日志限额。这样既有隔离性又不至于让特权容器泛滥。最后分享一个特别容易被忽视的小坑。如果你曾经 export 过 DOCKER_HOST 指向 DinD之后在宿主机上执行任何 docker 操作前先确认当前终端里的 DOCKER_HOST 是什么。我有一次在宿主机上想清理悬空镜像忘了环境变量还指向内层 daemon结果把 DinD 里的所有镜像全清了宿主机这边却一点没动。那感觉就像拿错了钥匙开了别人家的门还把人家屋子打扫了一遍。后来我把清理脚本全部改成显式指定 -H unix:///var/run/docker.sock或者干脆不依赖环境变量才算根治。希望这篇东西能让你少走几条弯路祝各位的 DinD 之旅顺利。