先说一个我见过很多次的场景某位同学在 Dashboard 面板上顺手点了一下容器的 Stop 按钮几秒后重新启动数据库起不来了日志里只剩半截没写完的 WAL。他非常困惑——我只是点了停止啊跟docker stop不是一回事吗从结果上看确实是一回事但从按下按钮到进程真正收到信号中间隔着的链路比大多数人想象的要长宿主机上的一个后台服务、一个轻量虚拟机、里面的一套守护进程、再到容器里的 1 号进程。这篇内容就围绕 Docker Desktop 这个桌面应用程序展开讲清楚它在整条容器链路里站在什么位置、它用什么方式管理容器的生老病死、资源配额是怎么被它分两层卡住的、网络和文件为什么在桌面端总比在服务器上更容易出岔子。适合刚上手 Docker、正在被为什么本机能跑、换台机器就不行折磨的开发者也适合那些天天用命令行、从没正眼看过这个桌面应用的人——你会发现它做的活远比一个 GUI 外壳多。1. Docker Desktop 在容器链路里究竟站在哪一环一个很常见的误解是Docker Desktop 就是把docker命令包了个界面。这个理解会直接导致后面一系列的排错方向错误。真实情况是桌面应用程序扮演的是一个调度者 翻译层 资源提供者的复合角色它先把一台轻量虚拟机拉起来再在虚拟机里装好完整的容器运行时最后把你终端里的docker命令通过一个 socket 转发进去。关键在于容器进程自始至终都跑在那台虚拟机里从来没有在你的宿主机操作系统上直接运行过。你在宿主机上打开任务管理器或者活动监视器翻遍进程列表也找不到容器里的那个 nginx 或者 java 进程——它压根不在这个进程空间里。理解了这一点后面所有为什么端口要映射为什么挂载目录权限怪怪的为什么内存限制有两层的问题答案都会顺理成章。1.1 一次 docker run 背后其实是五个角色的接力我们拆一条命令来看。你在终端敲下docker run -d -p 8080:80 nginx并回车整个过程大致是这样流转的第一步docker这个 CLI 客户端本身并不干活它只负责读取当前上下文context找到该连哪个 socket 文件然后把请求打包成 HTTP 请求发过去。第二步请求到达虚拟机内部的dockerd守护进程它负责解析参数、检查本地有没有对应镜像、没有就去仓库拉、有就直接进入创建流程。第三步dockerd把创建容器的任务交给containerd后者是真正管生命周期的中间层。第四步containerd拉起一个containerd-shim进程由它来调用runcrunc拿着 OCI 规范文件一项一项地配置 namespace隔离进程、网络、文件系统、配置 cgroup限制资源最后execve执行镜像里定义的入口命令然后runc自己退出。第五步containerd-shim成为容器内 1 号进程的父进程负责接管它的退出状态、回收僵尸进程、维护标准输入输出管道。这条链路解释了两件事。一件是为什么容器启动快——因为没有什么虚拟化引导过程就是普通的进程创建加上一堆内核配置。另一件是为什么容器进程死了以后docker ps还能看到它——因为containerd-shim还活着它把退出码交给了dockerd容器变成 Exited 状态留在了列表里。在桌面端这条链路的头尾各被加了一环头部是宿主机上的后台服务替你转发 socket尾部是虚拟机把容器产生的端口和文件流量再转回宿主机。1.2 桌面版和服务器版的差异得列个清单贴在墙上很多在我机器上好好的问题根子都在这个差异表里。我把它整理成下面这样建议真遇到诡异问题时对着扫一遍对比项Linux 服务器上的原生 DockerDocker Desktop 桌面端容器运行位置宿主机内核上直接跑一台轻量虚拟机或子系统内内核来源宿主机的内核虚拟机里那套独立内核--network host直接共享宿主机网络栈共享的是虚拟机的网络栈不是你的电脑文件挂载直接 bind mount要经过一层跨系统文件共享协议host.docker.internal默认不存在要手动加内置可用端口监听者docker-proxy或 iptables宿主机侧还有一个后台进程在监听资源上限cgroup 直接控宿主机先受虚拟机配额限制再受 cgroup 限制磁盘占用直接用宿主机的文件系统落在一个可增长但通常不自动收缩的镜像文件里这张表里最容易被忽略的是--network host那一行。很多教程里写用 host 网络模式性能最好在服务器上这话没错但在桌面端你用了 host 模式容器监听的是虚拟机的网卡宿主机上访问localhost:端口还是通的因为桌面端做了转发但局域网里另一台机器想访问就未必。我第一次踩这个坑时排查了半小时防火墙规则最后发现方向完全错了。1.3 Dashboard 上每个按钮落在命令行里是什么既然讲管理就绕不开那个图形面板。面板上的操作最终都会翻译成一条 CLI 命令内部走的是同一套 API对照一下能省很多事面板操作等价命令需要留意的细节Startdocker start name只重启容器不重新应用创建时的参数改动Stopdocker stop name默认给 10 秒宽限期超时强杀Restartdocker restart name本质是 stop start不是优雅重载配置Deletedocker rm name不加-v时匿名卷会残留Open in terminaldocker exec -it name sh镜像里没装 sh 或 bash 会直接失败Logsdocker logs -f name只看标准输出和标准错误看不到文件日志Stats 图表docker stats数据来自 cgroup 计数有采样间隔Inspectdocker inspect name面板只展示一小部分全量信息在这里我个人的习惯是面板用来看命令行用来动。因为面板的 Delete 按钮点下去太快了没有确认环节而docker rm至少还要敲一遍容器名给了大脑一个反悔的机会。这个差别在误删生产数据的场景下可能就是灾难和虚惊一场的区别。2. 容器生命周期管理桌面端每一步到底做了什么容器的状态机其实很简单created → running → paused → exited外加一个 removed 终态。但每个状态转换背后的动作差别很大而桌面应用把这些转换全都压缩成了一两个按钮便利的代价是丢失了中间信息。这一章我们把这几个转换拆开看。2.1 create 和 run 之间那道被忽略的缝绝大多数人只会用docker run从来不用docker create。但这两者的分离本身就是设计上的一个重要表达create只完成到容器对象建好了、可写层建好了、配置写好了一个进程都不会启动start才是真正去执行入口命令。这个分离的实际价值在于你可以在容器启动之前把一些必须提前准备好的东西挂上去。举个真实用过的例子——某个容器需要在启动瞬间就能访问一个特定网络里的另一个服务而默认网络在创建时就已经绑定了。做法是先docker create然后docker network connect把它接到目标网络再docker start。如果直接run容器可能在你连上网络之前就已经启动失败退出了。还有一类情况是需要在启动前修改配置。比如你希望容器的 1 号进程不是镜像默认那个可以先 create再通过docker update调整资源限制然后 start。桌面端的 Dashboard 里没有暴露 create 这个中间状态你在界面上点Run实际上是一步到位这也意味着你放弃了插入中间操作的机会。2.2 stop 的十秒宽限期坑了不止一个数据库docker stop的行为是这样的先给容器内的 1 号进程发SIGTERM然后开始计时默认 10 秒。如果进程在这段时间内自行退出stop 立即完成如果超时还没退就追加一发SIGKILL进程被内核直接干掉没有清理机会。问题恰恰出在1 号进程身上。Linux 里 PID 为 1 的进程内核对它的信号处理有特殊约定如果一个进程没有为某个信号注册处理函数那么它作为普通进程时该信号会被忽略但作为 1 号进程时行为还不完全一样很多 shell 作为 1 号进程时根本不会把SIGTERM转发给它的子进程。结果就是你的 Java 应用明明写了优雅停机的钩子但它不是 1 号进程1 号进程是那个启动脚本脚本没转发信号于是 10 秒后整个进程树被SIGKILL一锅端。这个现象在桌面端尤其容易被忽视因为 Dashboard 上点 Stop 的反馈太快了——它显示已停止的时候你可能以为一切正常实际上数据库的刷盘还没做完。我在一个测试环境里就遇到过容器化的关系型数据库在 Dashboard 上反复点停止启动十几次后某次启动直接报数据文件损坏最后只能从备份恢复。对应的实操手段有三个。一是把应用进程直接作为容器的入口命令用 exec 形式的ENTRYPOINT而不是 shell 形式让它成为真正的 1 号进程。二是在入口加一个信号转发器比如tini或者dumb-init它们会正确处理信号并回收子进程。三是针对明确知道需要更久停机时间的服务把宽限期拉长docker stop -t 60 my-db或者在建容器时就不依赖默认值用编排文件里的stop_grace_period: 60s固定下来。另外还有个反向技巧调试时如果想立刻杀掉容器不给它磨蹭的机会docker stop -t 0会直接用SIGKILL等价于docker kill。2.3 pause 冻结的到底是什么东西docker pause和docker stop完全是两回事但界面上两个按钮挨得很近误点概率不低。pause 的底层实现是 cgroup 的 freezer 控制器它把容器对应的 cgroup 里所有进程挂起进程不再获得 CPU 时间片但它们的内存、打开的文件描述符、网络连接全都原封不动地保留着。这个行为的直接后果是一个被 pause 的容器docker stats里 CPU 显示 0但内存占用纹丝不动它监听的端口依然被占用另一个容器想绑同一个端口照样会失败客户端发来的 TCP 连接不会被拒绝而是卡在那里等着直到容器被 unpause 才继续处理。所以如果你把 pause 当成暂停一下省点资源很可能会得到一个连接堆积、客户端大量超时告警的结果。pause 真正合适的场景其实很窄需要对容器做一次一致性快照之前或者要短暂地让所有线程停在同一个时间点上做调试。日常管理容器用 stop 就够确实想释放资源又不想丢状态那应该考虑的是把数据外置到卷里然后放心地 stop。2.4 删除容器时那些不会跟着消失的东西docker rm只删容器对象本身和它的可写层。有四类东西会留下来命名卷显式创建的 volume 永远不会被rm带走哪怕容器已经不存在了卷还在那儿占着磁盘。匿名卷这个最阴险。不指定-v的话匿名卷会变成孤儿卷docker ps看不到、docker volume ls里一堆 64 位哈希名的东西谁也不知道它们属于谁。自定义网络容器删了网络还在下次再建容器可能因为网段冲突或者残留的配置出问题。镜像层镜像一直躺在那儿除非你docker rmi。我吃过匿名卷的亏后来养成了一个条件反射docker run时一律加--rm让容器退出后自动清理真要保留数据的就用命名卷并显式写-v 卷名:路径绝不依赖匿名卷。清理时用docker volume ls -f danglingtrue找出所有无人引用的卷确认后批量删除。桌面端 Dashboard 的 Volumes 页签里也能看到这些但它不会告诉你哪个卷是孤儿得自己看引用数。3. 资源与性能为什么这里的限制是两层的在服务器上docker run -m 512m就是一道硬限制容器用超了就被 OOM Killer 干掉。在桌面端这个限制外面还套了一层——虚拟机的总配额。理解这个嵌套关系能解释一大堆我明明给了 2G 内存为什么还是被杀了的疑惑。3.1 CPU 和内存的配额第一道闸门在设置页里打开设置里的资源分配面板能看到 CPU 核心数、内存上限、交换分区、磁盘镜像大小这几项。这些数字控制的是那台虚拟机的总容量跟单个容器无关。默认值通常是比较保守的比如一半的物理核数、一半的内存桌面应用这么设定的思路是别把你整台电脑拖慢。于是就有了两层限制的叠加虚拟机有 8G 内存你在里面跑三个各限 2G 的容器理论上没问题但如果虚拟机只分到 2G那三个容器根本起不来第二个就会被拒。更隐蔽的情况是容器没设-m用多少吃多少直到把虚拟机内存吃满然后虚拟机内的内核触发 OOM Killer挑一个分数最高的进程杀掉。你看到的现象是某个容器莫名其妙 Exited退出码 137128 9表示被SIGKILL终止但docker inspect里看不出任何原因。排查这类问题的正确姿势是分两步。先看容器自己的限制有没有被触碰docker stats --no-stream docker inspect name --format {{.State.OOMKilled}}如果OOMKilled是 true那基本确认是内存问题。再看虚拟机整体的情况桌面端的资源面板里通常有个实时的总用量曲线或者直接在容器里跑一次free -m看虚拟机视角的内存。两层都看清楚才能判断是该调大虚拟机配额还是该给容器加限制。我个人的经验是给虚拟机分配内存时按所有容器限制值之和 × 1.3来估给单个容器设限制时务必留出 20% 余量给 JVM 堆外内存、页缓存和临时对象-m 512m配-Xmx512m是最常见的错误组合必挂。3.2 磁盘镜像文件为什么只长不缩桌面端的容器数据都存在一个单文件里——不同平台下叫法不一样一个稀疏格式的磁盘镜像或者虚拟硬盘文件。稀疏文件的特点是写多少占多少但删除之后空间不一定还回去。具体到 Docker 的使用上这意味着你docker build了十个镜像每个 1G镜像文件涨了 10G然后你docker rmi全删了理论上空间应该回来但那个镜像文件的物理占用往往还是 10G 左右。原因是虚拟机内的文件系统释放了块但虚拟磁盘的元数据没有做压缩宿主机的文件系统层面依然认为这些块是被占用的。要真正把空间还回去得走两步。第一步是在容器世界里清理让虚拟机内部的空间真正被释放docker system df # 先看清楚谁占得多 docker system df -v # 细到每个镜像、每个卷 docker builder prune -a # 构建缓存往往是大头 docker image prune -a # 慎用会删掉所有没被容器引用的镜像第二步是让虚拟磁盘文件收缩这一步在桌面应用的设置里通常有个清理/重置磁盘映像的入口或者通过宿主机的磁盘管理工具对那个文件做压缩操作。注意很多人在第一步之后就以为完事了结果du一看宿主机的空间并没有回来然后开始怀疑是不是清理命令没生效。还有一点值得提醒构建缓存是隐藏的磁盘黑洞。一个反复构建的项目即使每次都--no-cache中间层产生的缓存也可能堆到几个 G。定期看一眼docker system df的输出那个 BUILD-CACHE 一行经常是最大的数字。3.3 绑定挂载为什么比命名卷慢一个数量级这是桌面端最容易被低估的性能问题。在 Linux 服务器上把宿主机目录绑进容器本质上就是一次 mount几乎没有额外开销。但在桌面端文件得跨过操作系统边界宿主机上的文件系统 → 一层共享协议 → 虚拟机内的文件系统 → 容器看到的那一层。每一次读写都要走这条链。这个开销对小文件是毁灭性的。曾经有个前端项目代码放在宿主机目录里绑进容器跑构建一个几万文件的项目冷启动要两分多钟后来把代码复制进容器内的卷里跑降到了二十几秒。差别就是几千次小文件 stat 操作乘以跨系统调用的延迟。对应的优化策略按场景分纯代码开发需要热更新绑定挂载不可避免但要尽量减少容器内对挂载目录的密集扫描。把node_modules、target、build这类产物目录排除掉单独放进命名卷别让它们落在绑定目录里。数据持久化不需要宿主机直接编辑一律用命名卷性能好得多也不会因为宿主机的权限问题踩坑。需要两边都能访问的配置或产物绑定挂载可以接受但要意识到它是过渡方案不要指望高频读写。一个细节在子系统后端比如 Windows 上那套里把项目放在宿主机盘符下和放在子系统自己的文件系统里性能差了不止一倍。绑定时路径来源不同走的协议都不一样。这个差别在很多教程里没人提但它能省下你每天一小时的等待。4. 网络与端口桌面端误解最集中的一层如果你只从桌面端学过 Docker很容易对网络模型产生错误认知——因为桌面应用把很多复杂性藏起来了。而一旦换到服务器的命令行环境这些被藏起来的部分会一次性全部暴露出来。4.1 一条-p 8080:80背后其实有两段转发在服务器上端口映射做的事情大致是容器放在一个私有网段里通过 NAT 规则把宿主机的 8080 转发到容器的 80。这套规则由 iptables或者 nftables维护docker-proxy作为兜底处理一些特殊场景。在桌面端这条链路上多了一段虚拟机内部完成容器 80 到虚拟机网卡 8080 的映射虚拟机再把 8080 暴露给宿主机宿主机上的后台进程监听 8080 并转发进去。所以你看到的现象是netstat或者lsof在宿主机上能查到 8080 被占用但占用的进程并不是容器里的东西而是桌面应用的一个后台服务进程。这也是为什么端口被占用但杀掉那个进程会导致整个 Docker Desktop 重启的原因。这个认知带来的实际影响是如果 Docker Desktop 的后台服务状态异常即使容器在跑、虚拟机里的端口也正常宿主机上依然连不上。遇到这种情况先验证容器内部通不通——进容器里curl localhost:80通了说明问题在转发层不通说明问题在应用本身。这个二分法能省掉大量瞎猜。4.2host.docker.internal是桌面端独有的礼物容器要访问宿主机上的服务比如本机跑的数据库、调试代理、mock 服务在桌面端直接写host.docker.internal就行这个名字会被解析成虚拟机视角的宿主地址再由转发层落到你的电脑上。这个便利在服务器上是没有的。Linux 环境里这个名字默认解析不了得在建容器时手动注入一条映射docker run --add-hosthost.docker.internal:host-gateway myapphost-gateway是一个特殊值会被解析成宿主机的网关地址。我从桌面端迁移一套开发环境到服务器时就因为这个把所有连接配置写死了host.docker.internal而全线崩掉最后统一改成环境变量注入地址才解决。顺带说一句容器内的localhost永远指的是容器自己不是你的电脑。这个规则在所有平台上都一样但桌面端的用户因为经常用-p把端口映射到宿主localhost容易产生In 容器里写 localhost 也能连到外面的错觉。4.3 自定义 bridge 网络里的名字解析默认的 bridge 网络有个很大的限制容器之间只能用 IP 互相访问不支持用容器名解析。这意味着你在一个容器里连另一个容器写redis:6379是解析不了的得写172.18.0.3:6379——而这个 IP 每次重建都可能变。解决办法是自己建一个自定义 bridge 网络把相关容器都接进去。这套网络内置了一个 DNS 服务监听在 127.0.0.11这个地址在容器里看不到进程但cat /etc/resolv.conf能看到它会把网络内所有容器的名字、别名、容器 ID 都注册进去。docker network create app-net docker run -d --name cache --network app-net redis docker run -d --name api --network app-net -p 8080:8080 myapi这样api容器里写cache:6379就能连上。桌面端的 Dashboard 在网络页签里能看到网络拓扑和成员但它不会告诉你这两个容器接了不同网络所以互相看不见这个信息得用docker network inspect 网络名自己看。一个常见的坑是容器重启后 IP 变了但你的应用配置里写的是之前的 IP。用自定义网络的 DNS 就能规避如果确实需要固定 IP记住要配合自定义网络并显式指定--ip默认 bridge 网络不支持这个参数。5. 五类高频故障以及我实际走的排查链路这部分是这篇文章里最想写实的部分。下面每一个都是我或者身边人真实撞过的排查顺序也按实际走过的来写而不是按正确答案倒推。5.1 端口被占用但查不到是谁占的现象启动容器时报bind: address already in use重启一次可能又好了或者换个端口就好了完全没规律。排查路径第一步不要急着找进程先确认这个端口在宿主机上到底有没有监听者。用lsof -i :8080或者netstat -ano | findstr 8080看一下。如果显示被桌面应用的后台进程占用那说明是上一次的容器还没彻底释放——桌面端的转发层有时会在容器停止后延迟一小段时间才释放端口。第二步docker ps -a看看是不是有个同名容器还在 Exited 状态但网络命名空间没清理干净docker rm掉之后通常就好。第三步如果宿主机上确实看得到监听但杀掉没用那就进虚拟机层面看有些容器用了--network host它占的是虚拟机的端口而这个端口可能被另一个 host 模式的容器占着docker ps里根本看不出来。我的经验是桌面端遇到这种幽灵占用先停掉所有容器再重启 Docker Desktop 本身八成能解决剩下两成是宿主机上真有别的程序占了端口跟 Docker 无关。5.2 容器秒退日志一片空白现象docker run之后docker ps里看不到docker ps -a显示 Exited(0) 或者 Exited(1)docker logs什么都没输出。这种情况在两个场景里最常见。一个是镜像的默认命令本身就是一个需要交互终端的 shell容器没有分配 ttyshell 直接读到 EOF 就退出了。判断方法是docker inspect看Config.Cmd和Config.Entrypoint如果是[sh]或者[bash]之类基本确定。加上-it重新跑就能看到交互界面。另一个是进程的启动失败输出被写到了容器的日志系统之外比如写进了文件、写到了 syslog、或者被 entrypoint 脚本重定向到了/dev/null。这时候docker logs当然什么也看不到。做法是覆盖入口命令先起一个能停留的 shelldocker run -it --entrypoint sh myimage进去之后手动执行原来的启动脚本错误就会打在屏幕上。这个技巧我用了无数次比盯着日志文件猜快得多。还有一个变体是容器以 Exited(0) 退出——退出码 0 说明进程正常执行完了那它本来就是一个一次性任务型镜像不是服务你只是用错了。5.3 磁盘爆了但宿主机的空间没释放现象docker build报no space left on device或者容器写文件失败但宿主机上看磁盘还有几十 G 空闲。这时候要看的是虚拟机的磁盘配额不是宿主机的空闲空间。桌面端设置里那一项磁盘镜像大小默认值往往只有几十 G构建几个大镜像就满了。第一步先看 Docker 自己的账本docker system df -v这个输出会按镜像、容器、卷、构建缓存四类列出大小。我见过的最大占比通常来自三处多层构建留下的中间镜像、--no-cache也清不掉的构建缓存、以及一堆没人引用的孤儿卷。清理要按顺序来先清构建缓存再清镜像因为构建缓存本身可能引用着中间层。第二步才是调整虚拟机的磁盘配额很多桌面端设置里支持在线调整但也有版本需要重启生效。这里要提醒的是把配额调小是有风险的如果虚拟机内部实际使用了 40G 而你调到 30G可能出现不可预期的文件损坏宁可先清理再加。5.4 桌面应用卡在 Starting 状态起不来这个故障的表现很统一图标一直在转、docker ps报连接不上 socket、命令行所有操作都超时。排查顺序我总结了一套基本按成本从低到高排。第一看看是不是有大量容器在跑且资源吃满这种情况关掉几个通常就能恢复。第二重启桌面应用本身注意要完全退出而不是关窗口——托盘里的图标右键退出等几秒再启动。第三如果重启无效说明是虚拟机层面的问题得把虚拟机彻底关闭再启。在子系统后端上这一步对应的是关闭整个子系统的命令在虚拟机后端上得通过活动监视器或者任务管理器找到对应的虚拟化进程手动结束。第四如果还是不行就该看日志了。桌面端的日志目录通常在用户目录下的应用数据里主机侧日志和应用侧日志分开存放先看主机侧那份通常能直接看到某端口被占用内存不足虚拟磁盘损坏这类明确原因。我在一次磁盘几乎写满的情况下遇到过反复卡在 Starting日志里清楚地写着无法创建虚拟磁盘清出空间后立刻恢复。5.5 挂载进来的目录在容器里是空的现象-v /path/on/host:/app/data之后容器里/app/data空无一物或者反过来宿主机上的文件被容器里的内容覆盖了。这里有几个层层递进的原因。第一层最常见宿主机路径写错了Docker 会把不存在的绝对路径当成要新建的目录默默创建一个空目录挂上去于是容器里看到的就是空的。所以绑定挂载前先ls一下源路径确认存在这个习惯能省掉无数次排查。第二层是文件共享的授权问题。桌面端在挂载宿主机目录时需要那一层文件共享服务有权限读取该路径用户主目录之外的路径比如外接磁盘、系统级目录往往需要手动在设置里添加到共享列表。没加的话表现就是目录存在但内容读不到。第三层是路径语义的差异有些系统上的路径分隔符、盘符、符号链接的解析规则不一样跨系统挂载时要格外小心。还有一个反直觉的现象如果挂载的是一个空目录容器里对应路径原有的内容会消失。这不是删除是挂载覆盖——镜像里的那一层被挂载点遮住了卸掉挂载就能看到。很多人第一次遇到以为镜像出了问题其实这是 mount 的标准行为。6. 把桌面端用顺手我固定在做的几件事最后一章不写总结写几个具体动作。这些是我在自己机器上固定下来的习惯谈不上最佳实践但确实让日常使用少了很多意外。6.1 用 context 管多套运行环境别去改 socket 路径同时连本机 Docker Desktop、远端测试机、团队私有仓库的构建环境时最原始的做法是改环境变量DOCKER_HOST但这玩意儿是全局的、容易被忘掉、一忘就命令敲到错误的环境上。更稳妥的是用 contextdocker context ls docker context use desktop-linux docker context use remote-test docker --context remote-test psdocker context的好处是每个环境的连接信息是独立的当前用哪个一目了然临时切一次用--context参数就行不用改全局状态。桌面应用安装后通常会自动注册一个 context你不用手写配置。我吃过改环境变量的亏——在本地跑了一条docker system prune结果发现变量还指向测试机那一刻的冷汗是真的。6.2 给每个项目一个独立的项目名如果用到编排文件COMPOSE_PROJECT_NAME这个变量一定要设。默认情况下项目名取自目录名两个不同仓库的目录都叫app的话容器名、网络名、卷名会撞在一起表现就是启动时报名字已存在或者更糟——数据写进了另一个项目的卷里。在项目的环境变量文件里加一行就行名字我习惯用项目名-用途的格式一眼能看出归属。这样docker compose down -v清理时影响范围严格限定在本项目内不会误伤隔壁正在调试的环境。桌面应用的面板会按项目分组展示容器名字起得清楚面板本身就是一份可读的环境清单。6.3 清理的节奏比清理的命令更重要清理命令谁都会敲难的是形成节奏。我的做法是按每次、每周、每月三档来分每次跑完一次性任务容器加--rm构建加--pull之外不做额外动作。每周看一眼docker system df如果构建缓存超过 3G 就清一次顺带清掉悬空镜像。每月审视一次卷和网络把引用了零个容器的卷列出来逐个确认跑一次完整清理然后检查宿主机的磁盘占用有没有真正回落。最后这一条最容易忘。清理命令跑完不代表宿主机的空间回来了得看那个虚拟磁盘文件的物理大小。如果清了 20G 但文件大小没变说明需要做一次磁盘压缩才能真正回收。我一般把这一步和月度重启桌面应用合并在一起做反正都要重启省一次折腾。一个很小的习惯但价值很高每次动破坏性命令之前先跑一次docker ps -a和docker volume ls把结果截个图。真删错了至少知道删了什么还能重建。
阅读完成 · 觉得有帮助?