
1. 别急着敲命令先把Docker的三驾马车装进脑子我见过太多人打开Docker官方文档就一头扎进docker run结果三天后还在跟容器状态搏斗。原因几乎都一样对Docker最核心的三个抽象概念没建立直觉。这三个概念就是——镜像Image、容器Container、数据卷Volume。你可以把镜像想象成一个只读的安装光盘里面打包了操作系统的一个最小子集、运行环境、依赖库和应用代码。比如mysql:8.0这个镜像里面就是MySQL 8.0的完整运行环境但它是静态的、冻结的。容器则是这张光盘被运行起来后形成的动态沙箱。同一个镜像可以启动出任意多个容器彼此隔离互不干扰。打个比方镜像是一份菜谱容器是按菜谱实际炒出来的那盘菜菜谱永远不变菜可以炒无数次每次出锅的状态都独立。数据卷则是解决容器删了数据就没了这一致命问题的关键。容器是临时性的一旦删除容器内所有写入的文件都会跟着消失。数据卷是独立于容器生命周期之外的存储空间相当于把菜盘从炒菜锅里抽出来单独存放——锅坏了可以换一口新的盘子里的菜还在。这就是Docker的三驾马车镜像管打包容器管运行数据卷管持久化。从本质上看Docker做的事就一句话把这软件在我机器上能跑变成这软件在任何装了Docker的机器上都能跑。它通过Linux内核的namespace做资源隔离、cgroup做资源限制让多个容器安全地共享同一个内核。Windows和macOS上的Docker Desktop则是通过轻量级虚拟机间接实现同样的效果这一点后面会展开。搞清楚这三个概念再动手你会发现后面所有命令都只是在这三层逻辑上做操作。否则你只是会敲命令换个场景照样懵。2. 安装Docker的拦路虎Windows环境下的Virtualization与WSL2问题热搜词里密密麻麻全是安装问题virtualization support not detected、Docker Desktop failed to start、failed to connect to the docker api at npipe。这些都是Windows用户在安装Docker Desktop时最容易撞上的墙也是最劝退新手的地方。2.1 先搞懂Docker Desktop在Windows上的运行逻辑Docker Desktop在Windows上不是直接跑Linux容器的。Linux容器的内核是Linux的Windows内核跑不了。Docker Desktop的做法是在Windows里内置一个极轻量的虚拟机层基于WSL2或Hyper-V在这个虚拟机里跑一个Linux发行版Docker引擎跑在这个Linux里你执行的docker命令通过管道转发给虚拟机里的引擎去执行。这就是为什么磁盘、WSL2、虚拟化这三者任何一个出问题Docker Desktop都起不来。很多新手以为Docker Desktop是个普通Windows软件统一装完就万事大吉实际它是个套中套结构。2.2 virtualization support not detected的完整排查链路这个报错的字面意思是没检测到虚拟化支持但绝大多数情况下你的CPU是支持虚拟化的只是没在BIOS里打开、被Hyper-V或者内核隔离抢占了、或者WSL2需要的功能没开全。按下WinR输入msinfo32回车打开系统信息看基于虚拟化的安全性和虚拟化固件中已启用这两项。如果虚拟化固件中已启用为否需要进BIOS开机按F2或Del不同主板不一样找到Intel VT-x或AMD-V/SVM选项把它设为Enabled保存重启。这一步没法用软件绕过。如果BIOS已经开了虚拟化但Docker还是报同样错误优先检查Windows功能里是否开启了虚拟机平台和Linux子系统。控制面板进入启用或关闭Windows功能勾选虚拟机平台和适用于Linux的Windows子系统重启。然后用管理员身份打开PowerShell执行wsl --set-default-version 2把WSL默认版本设为2。WSL1和WSL2差异巨大Docker Desktop必须用WSL2才能正常跑。还有一个容易被忽略的地方如果你电脑装了360、鲁大师之类带有游戏加速或虚拟化优化功能的安全软件它们可能会动态调整虚拟化设置导致Docker Desktop时好时坏。我遇到过不止一个学生在安装时死活报错最后发现是安全软件的系统加速开关在捣乱。装Docker Desktop之前先把这类功能关掉。2.3 failed to connect to the docker api at npipe八成是服务没起来还有一批人装上Docker Desktop后执行docker ps收到类似failed to connect to the docker api at npipe:////./pipe/dockerDesktopLinuxEngine这样的错误。这个报错的根因通常是Docker Desktop的Linux引擎没正常启动或者启动到一半挂了。最直接的判断方式看Docker Desktop的鲸鱼图标——如果一直在转圈说明引擎还在启动中如果图标是红色的说明启动失败。点击Troubleshoot图标小扳手进入诊断页点Restart强制重启Docker Desktop然后耐心等30秒左右再执行docker version看客户端和服务端的响应。如果反复启动失败建议去Settings菜单最下面的Reset to factory defaults做一次出厂重置。注意这次重置会清掉你本地的全部镜像和容器如果里面有你重要的数据卷请先备份好。做之前先docker volume ls看一眼有哪些数据卷该备份的提前用docker run --rm -v 卷名:/data -v 本机目录:/backup alpine tar czf /backup/volume.tar.gz -C /data .备份到本地。2.4 一条命令验证安装是否真的成功装完之后别急着跑MySQL先执行docker run --rm hello-world。这条命令会从Docker Hub拉取一个极小的hello-world镜像然后运行它并输出一段提示信息。如果这段输出正常出现说明拉取、创建、运行、删除--rm用完即删这条完整链路全部正常。我自己每装一台新机器都会用这条命令做冒烟测试几秒钟就能确认Docker引擎工作正常。3. 镜像加速与拉取失败的自救手册从配置镜像源到unexpected eof装好Docker之后新手99%会遇到的第二个坎就是镜像拉取。Docker Hub的源站服务器在海外直接从源站拉大镜像不仅慢还经常拉一半断掉。3.1 镜像加速器的配置方法Docker早就支持配置registry mirror镜像加速器了就是让Docker去国内或全球分布的镜像站点拉取绕开直连慢的问题。以Docker Desktop为例在Settings的Docker Engine配置里编辑json配置{ registry-mirrors: [ https://docker.m.daocloud.io, https://dockerproxy.com, https://docker.nju.edu.cn ] }配置完成后点击Apply Restart然后执行docker info在输出里找Registry Mirrors那一栏能看到你配置的加速器地址就说明生效了。Linux上的Docker则在/etc/docker/daemon.json里配置同样的内容改完执行systemctl restart docker重启服务。注意加速器本质上相当于一个大缓存如果你需要的镜像在加速器上没有缓存它还是会回源去Docker Hub拉取。所以配置了加速器不代表100%秒下只代表大部分热门镜像的下载速度会有极大改善。3.2 docker: unexpected eof到底是怎么回事热搜词里有docker: unexpected eof这个报错很多人遇到过但完全摸不着头脑。它的真实含义是Docker在拉取镜像的某个层layer时连接被远端意外关闭了——下载断了一半。原因无非三类网络不稳定导致连接重置、镜像层特别大导致下载超时、或者是某些安全软件拦截了长连接。解决办法也很直接先docker rmi删除那个拉取失败的半截镜像一般是none标签的废弃层。降低并发下载层数在daemon配置里加上max-concurrent-downloads: 3。默认是3如果网络条件差改成1试试虽然慢但至少稳定。检查加速器配置是否有效很多加速器限流或者失效后会频繁断连。如果一直失败换用不同的加速器或者等待非高峰时段再拉取。3.3 关于龙芯和国产CPU架构的镜像选择热搜词里还有个龙芯 docker这个值得单独说一下。Docker镜像本质上是针对特定CPU架构编译的x86_64的镜像不能直接在ARM64上运行。如果你的机器是龙芯LoongArch或者其他国产CPU架构拉镜像时要注意用--platform参数指定平台docker pull --platform linux/loong64 redis:7.2但说实话LoongArch的镜像生态目前还比较有限很多镜像根本没有专门为它构建的版本。这时候更稳妥的方案是检查系统是否支持运行x86_64模拟层有些国产Linux发行版通过多架构支持运行x86_64镜像或者在Dockerfile里用多阶段构建时明确指定FROM --platform$TARGETPLATFORM来做多平台构建。这块后面讲微服务部署时会再展开。4. 实战跑通第一个容器以MySQL 8.0为例的一次完整从0到14.1 为什么新手第一个容器要选MySQLMySQL是检索热度最高的实战项目也是最适合入门的容器化对象。原因很现实MySQL有清晰的端口3306、明确的数据持久化需求data目录、有初始化配置需求my.cnf、还有一个隐藏问题root用户远程访问权限麻雀虽小五脏俱全。跑通一个MySQL容器等于同时掌握了端口映射、数据卷、环境变量、日志查看、进入容器执行命令这五类最高频的Docker操作。4.2 从docker run到docker compose的最短路径先看最简单的直接运行方式docker run -d \ --name mysql8 \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORDYourpass123 \ -e MYSQL_DATABASEtestdb \ -v mysql_data:/var/lib/mysql \ mysql:8.0逐个解释-d后台运行。--name mysql8给容器起个名字后面所有操作都靠这个名字指代容器。-p 3306:3306宿主机端口:容器内端口。宿主机上的3306端口转发到容器的3306端口。如果宿主机3306被占用了可以改成-p 3307:3306。-e传入环境变量。MySQL官方镜像读取MYSQL_ROOT_PASSWORD作为初始root密码读取MYSQL_DATABASE自动创建初始数据库。-v mysql_data:/var/lib/mysql把命名卷mysql_data挂载到容器内MySQL的数据目录。这个命名卷由Docker管理存储在Docker的数据根目录下路径不用你关心Docker会妥善保管。mysql:8.0镜像名称和标签。latest标签在新手阶段最好避免因为latest是滚动更新的今天装的8.0.33过几个月pull就变成8.0.36了行为可能出现细微差异。指定具体小版本更可控。启动后怎么确认它真的在跑执行docker ps看到mysql8容器的状态为Up端口映射显示0.0.0.0:3306-3306/tcp就说明容器已成功运行。然后可以用宿主机上的mysql客户端连接mysql -h 127.0.0.1 -P 3306 -u root -p输入刚才设置的密码如果能进入数据库恭喜你第一个容器就跑通了。4.3 我用得最多的三组容器操作进容器、看日志、拷文件跑通之后还要掌握三组操作。第一组是进入容器内部查看环境docker exec -it mysql8 bash这个命令会打开一个交互式shell让你进入容器内部。进去之后你可以ls看目录结构、执行mysql命令、查看配置文件。注意很多官方镜像默认不带vim容器里也不该装编辑器因为你改的任何东西在容器重建后都会丢。第二组是查看容器日志排错docker logs -f mysql8-f参数会实时尾随日志输出。MySQL启动卡住、密码错误、磁盘满了都能从这里看到线索。第三方应用的容器排错docker logs是第一个要看的地方。第三组是容器和宿主机之间拷文件docker cp /宿主机/路径/文件 mysql8:/容器内/路径/ docker cp mysql8:/容器内/路径/文件 /宿主机/路径/比如你要把本机的dump.sql灌进MySQL容器执行先docker cp dump.sql mysql8:/tmp/dump.sql再docker exec mysql8 mysql -uroot -pYourpass123 参数把SQL导入。4.4 配置文件的挂载与root远程访问权限坑实际工作中很少只用一个裸MySQL容器。往往需要提供自定义的my.cnf配置比如修改字符集和排序规则docker run -d \ --name mysql8 \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORDYourpass123 \ -v /opt/mysql/conf.d:/etc/mysql/conf.d \ -v mysql_data:/var/lib/mysql \ mysql:8.0这里用绑定挂载bind mount把宿主机上/opt/mysql/conf.d目录下的*.cnf文件挂载进容器。和命名卷的区别是你直接在宿主机上用编辑器改这个目录下的文件MySQL容器内的配置就会同步变化需要docker exec mysql8 mysqladmin -uroot -pYourpass123 reload或者重启容器才能生效。设置完配置之后还有一个高频坑MySQL 8.0默认root用户只允许localhost连接。通过-p 3306:3306映射后外部客户端连接会被拒绝。这时要么建一个专门用于远程访问的用户docker exec mysql8 mysql -uroot -pYourpass123 -e CREATE USER appuser% IDENTIFIED BY Apppass123; GRANT ALL PRIVILEGES ON testdb.* TO appuser%; FLUSH PRIVILEGES;要么把root的host改成允许任意主机。生产环境我强烈建议用前者——只给最小权限root绝不开放远程。5. 从跑起来到用得稳数据卷、备份恢复与容器文件系统的边界5.1 容器内改完即丢的本质原因很多新手第一次踩到数据丢失的坑是这样的启动了一个MySQL容器在数据库里建了张表第二天发现容器不在了可能手动删了也可能是Docker Desktop被清理了一查表没了数据没了。这是因为你全程没有挂载任何数据卷所有数据都写在容器可写层里。容器一旦被删除这个可写层连同里面的所有数据都会被清理掉。记住一条铁律容器是无状态的一切需要持久化的数据必须放在数据卷或绑定挂载里。同理容器内装的软件包、改的配置文件在容器重建后都会消失你要么改成挂载配置文件要么把配置写进Dockerfile通过自定义镜像固化下来。5.2 命名卷与绑定挂载的适用场景梳理存储方式特点适用场景命名卷Named Volume通过-v 卷名:/容器内路径创建实际存储在Docker管理的专有目录中路径由Docker维护数据库数据文件、不希望你手动碰的目录绑定挂载Bind Mount通过-v /宿主机绝对路径:/容器内路径创建文件直接放宿主机指定路径下配置文件、代码目录开发场景常用命名卷的好处是你不必关心文件具体存在哪Docker在备份工具docker run --rm -v 卷名:/data -v 本机目录:/backup alpine tar czf /backup/volume.tar.gz -C /data .里封装好了对卷的直接访问。坏处是你不容易从宿主机直接找到文件位置虽然可以docker volume inspect查到实际路径。绑定挂载的好处是路径一目了然开箱即用用文本编辑器就能改。代价是权限问题更容易出现——容器内用户和宿主机用户uid不一致时可能会遇到Permission denied。比如容器内MySQL进程以mysql用户运行uid 999你宿主机上把配置目录的属主设为root容器可能读不了配置文件。解决方法是chown 999:999把目录属主改成容器的用户uid或者用-u参数指定运行用户。5.3 一套简单的MySQL容器备份恢复操作数据卷备份的思路是启动一个临时容器挂载数据卷用tar把目录打包复制到宿主机。恢复反过来把宿主机上的压缩包解压到临时容器挂载的数据卷目录里。备份docker run --rm -v mysql_data:/data -v $(pwd):/backup alpine sh -c cd /data tar czf /backup/mysql_backup.tar.gz .恢复docker run --rm -v mysql_data:/data -v $(pwd):/backup alpine sh -c cd /data tar xzf /backup/mysql_backup.tar.gz恢复前建议先把正在运行的MySQL容器停掉docker stop mysql8避免恢复过程中出现数据写入。恢复完再启动容器。这套思路不只适用于MySQL任何数据卷都可以用同样的方式备份和迁移。6. 一人一套环境docker-compose编排MySQL与Redis主从6.1 为什么说多容器场景必须用compose只部署单个MySQLdocker run完全够用。一旦涉及多个容器比如数据库缓存应用docker run的命令就会变得又长又难以维护。你还要关心容器之间的网络通信、启动顺序依赖、环境变量管理。这就是docker compose的用武之地。它把多个容器的配置统一写在一个docker-compose.yml里用docker compose up -d一条命令启动整个应用栈用docker compose down一键清理。而且compose会自动创建一个默认网络所有在同一个compose文件里定义的服务都通过服务名互相访问省去了手动--link或者建自定义网络的麻烦。6.2 一个含MySQL与Redis主从的compose文件实例直接给一个生产可用的例子services: mysql: image: mysql:8.0 container_name: app-mysql restart: always environment: MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD} MYSQL_DATABASE: appdb TZ: Asia/Shanghai command: - --character-set-serverutf8mb4 - --collation-serverutf8mb4_unicode_ci ports: - 3306:3306 volumes: - mysql_data:/var/lib/mysql - ./mysql/conf.d:/etc/mysql/conf.d:ro healthcheck: test: [CMD, mysqladmin, ping, -h, localhost, -u, root, -p${MYSQL_ROOT_PASSWORD}] interval: 10s timeout: 5s retries: 5 redis-master: image: redis:7.2 container_name: app-redis-master restart: always command: [redis-server, --appendonly, yes, --requirepass, ${REDIS_PASSWORD}] ports: - 6379:6379 volumes: - redis_master_data:/data redis-slave: image: redis:7.2 container_name: app-redis-slave restart: always depends_on: - redis-master command: [redis-server, --slaveof, redis-master, 6379, --masterauth, ${REDIS_PASSWORD}, --requirepass, ${REDIS_PASSWORD}] ports: - 6380:6379 volumes: - redis_slave_data:/data volumes: mysql_data: redis_master_data: redis_slave_data:文件里几个细节值得说明restart: always容器异常退出后Docker会自动拉起对生产环境非常重要的自愈能力。./mysql/conf.d:/etc/mysql/conf.d:ro:ro表示只读挂载容器内不会误改配置文件。healthcheck定义健康检查命令compose里其他服务可以用depends_on的condition: service_healthy来等待MySQL真正就绪再启动。环境变量用${}引用配合当前目录下的.env文件使用。.env里写MYSQL_ROOT_PASSWORD...、REDIS_PASSWORD...不要把明文密码硬编码进compose文件提交到Git仓库。6.3 启动顺序的真实坑compose第一次启动顺序是按定义顺序来的但它不会等MySQL完全就绪再启动下一项。如果应用服务比如Java后端在MySQL还没初始化完成时就尝试连接数据库就会报连接失败。常规做法是依赖healthcheckservices: app: image: myapp:latest depends_on: mysql: condition: service_healthy只有当MySQL的mysqladmin ping成功时才会启动应用容器。Redis主从也有一个类似的问题从节点启动时要能解析redis-master这个主机名这依赖于compose网络里的DNS解析。在同一个compose项目中容器会自动注册服务名redis-slave容器可以通过redis-master这个域名访问主节点。6.4 青龙面板依赖管理的经验容器内装依赖不如构建自定义镜像热搜词里有青龙面板依赖管理这是很多人用Docker跑面板时痛点最集中的地方。青龙面板qinglong是一个定时任务管理面板很多用户在面板里安装python、nodejs、linux依赖时遇到各种问题依赖装不上、装了版本不对、更新后依赖全丢。根因在于面板容器基于某个基础镜像打包容器内的包管理器apk/pip/npm版本是那个镜像构建时的快照。你在容器内docker exec进去装依赖装是装上了但只要容器被重建面板更新、Docker Desktop重启、docker compose up -d --force-recreate容器内的一切改动全部灰飞烟灭。正确做法是把依赖写进Dockerfile构建成自己的镜像FROM whyour/qinglong:latest RUN npm install -g pnpmlatest RUN python3 -m pip install --upgrade pip然后docker build -t my-qinglong:latest .compose文件里的镜像名改成my-qinglong:latest。这样每次重建容器依赖都还在因为依赖被固化进了镜像层。类似的逻辑适用于所有看起来需要在容器里装点什么的面板类应用。7. 生产环境部署背后的三个细节GitLab、微服务打包与私有仓库7.1 用Docker部署GitLab需要预先想清楚的事热搜里docker安装gitlab热度很高但GitLab是我见过最容易被低估资源消耗的容器应用。官方推荐至少4核4G内存实际跑起来配合Jemalloc优化稳定吃2G以上内存跑不掉。而且GitLab容器最麻烦的是配置文件、日志、数据三块都要挂载到宿主机否则升级容器时全丢。我实际用的GitLab compose片段services: gitlab: image: gitlab/gitlab-ce:16.10.0-ce.0 container_name: gitlab restart: always hostname: gitlab.example.com ports: - 80:80 - 443:443 - 2222:22 volumes: - gitlab_config:/etc/gitlab - gitlab_logs:/var/log/gitlab - gitlab_data:/var/opt/gitlab shm_size: 256mshm_size是容易被忽视的参数——GitLab内部使用Puma和Sidekiq默认/dev/shm只有64M跑一段时间就频繁报Shared memory相关的错误。调成256m能省掉很多莫名其妙的崩溃。7.2 IDEA里给Spring Boot项目打包Docker镜像的两种思路热搜词idea 打包docker镜像来自Java开发者的日常。IDEA 2020之后自带Docker插件你可以在pom.xml里配置dockerfile-maven-plugin或com.spotify dockerfile-maven-plugin但更简单的思路是两步走第一步直接生成可执行jar包用mvn clean package -DskipTests。第二步在项目根目录写一个DockerfileFROM openjdk:17-jdk-slim WORKDIR /app COPY target/myapp.jar /app/myapp.jar EXPOSE 8080 ENTRYPOINT [java, -jar, /app/myapp.jar]然后在IDEA右侧的Docker面板里右键Dockerfile文件选择Build Image。或者命令行docker build -t myapp:1.0.0 .这里有两个细节容易被忽视。一是基础镜像用openjdk:17-jdk-slim比较稳妥-slim版本体积小、工具链精简够运行jar即可二是IDEA Docker面板默认连接的是Docker Desktop如果你的项目在WSL2里构建注意镜像上下文路径是WSL文件系统还是Windows文件系统路径差异会导致COPY找不到文件——这也是COPY failed类报错的常见来源。7.3 私有Docker Registry为什么你的团队需要一个热搜里有docker registry 镜像、docker仓库这其实指向的是企业/团队内部共享镜像的需求。镜像文件动辄几百MB甚至上GB大家各自从Docker Hub拉一遍既慢又浪费带宽而且生产环境不适合直接依赖公有仓库里的镜像公有镜像可能被篡改也可能因为版本漂移导致环境不一致。搭建一个registry非常轻量docker run -d \ --name registry \ -p 5000:5000 \ -v registry_data:/var/lib/registry \ registry:2然后打tag推送docker tag myapp:1.0.0 localhost:5000/myapp:1.0.0 docker push localhost:5000/myapp:1.0.0局域网内其他机器拉取时把镜像地址换成registry所在机器IP:5000/myapp:1.0.0注意Docker默认只信任HTTPS仓库HTTP仓库需要在每台客户机的daemon配置里加上insecure-registries: [192.168.1.10:5000]。提示用Harbor这类带Web界面、镜像扫描和权限管理的产品做企业级私有仓库会省心很多但它本身也是若干个Docker容器部署时同样遵循compose的思路。7.4 微服务部署的常规打法一个服务一个镜像compose统一编排热搜词docker部署微服务项目是后端开发者最常见的场景。微服务拆分之后每个服务各自拥有Dockerfile各自构建出镜像。部署时把这些服务统一写进一个compose文件Nginx或网关作为入口内部服务不对外暴露端口只用compose内部网络互相调用。原则上有几条经验每个服务的基础镜像尽量保持一致比如统一用openjdk:17-jdk-slim避免每层语言运行时版本不一致的排查地狱。内部服务不需要ports映射只要compose网络内可达即可只在网关或需要对外暴露的服务上映射端口。服务之间的配置数据库连接串、Redis地址通过环境变量注入不要写死在代码里和镜像里这样同一份镜像可以在测试环境和生产环境用不同环境变量跑出不同行为。8. 权限、日志与清理让Docker环境长期健康运行的三类日常运维8.1 docker权限错误的解决思路热搜词docker权限错误怎么解决指向的通常是Linux尤其是Ubuntu用户遇到的问题Got permission denied while trying to connect to the Docker daemon socket。原因是Docker守护进程以root用户运行Unix套接字/var/run/docker.sock默认只允许root访问。普通用户要执行docker命令要么每走一步都加sudo要么把用户加入docker组。sudo usermod -aG docker $USER newgrp docker执行完后重新打开终端普通用户就能直接执行docker命令了。这里有一个安全提醒加入docker组的用户等于拥有了root权限因为docker允许挂载宿主机目录、进入容器执行命令所以在生产环境的多人服务器上给谁加docker组需要谨慎。如果用户已经加入docker组还是提示权限拒绝检查/var/run/docker.sock的权限和Selinux/AppArmor配置有些安全策略会限制套接字的访问。8.2 日志文件的膨胀远比想象中快容器内的应用默认把所有stdout和stderr输出到容器的JSON日志文件里Docker把这些日志存起来供docker logs查看。但这个文件会持续增长一个高频打印日志的应用一天吃掉几个GB日志文件很常见。这也是磁盘满了问题的主要来源。最省事的做法是在daemon配置里加日志轮转{ log-driver: json-file, log-opts: { max-size: 10m, max-file: 3 } }这样每个容器日志文件达到10M就轮转最多保留3个文件。对大部分生产场景够用了。如果容器日志特别重要需要长期留存那应该把日志收集到ELK或Loki这类集中日志系统而不是堆在Docker的日志文件里。8.3 容器、镜像、构建缓存的定期清理开发环境跑久了docker system df一看镜像、容器、构建缓存、数据卷加起来几百GB占用一点不奇怪。下面三条命令对我特别有用# 清理所有已停止的容器 docker container prune -f # 清理所有未被任何容器使用的匿名卷 docker volume prune -f # 清理所有悬空镜像没有tag且未被使用的镜像层 docker image prune -f慎用docker system prune -a它会删掉所有未在运行的容器关联的镜像你可能需要重新拉取它们。一开始用局部清理就够安全。8.4 Docker服务启动失败的常规排查热搜词Docker服务启动失败通常出现在Linux服务器上现象是systemctl start docker后服务状态还是failed。排查顺序我一般是这样第一步看服务状态systemctl status docker它会告诉你启动失败的错误码和概要。第二步看daemon日志journalctl -u docker -n 100真实的报错信息比如端口被占用、iptables配置错误、/var/lib/docker目录权限不对都在这里。第三步检查daemon.json配置是否有语法错误一个多余的逗号都会导致启动失败。第四步检查存储驱动是否被正确加载overlay2驱动缺失时Docker会报类似的启动错误。最让人挠头的一类情况是配置文件看着没问题docker就是起不来。这时候试试dockerd --debug前台运行它会把启动过程中的每一步打印出来通常能看到真正的阻塞点。Windows上的Docker Desktop服务启动失败则优先看系统事件查看器的Windows日志和Docker Desktop的Troubleshoot诊断页前面第2节已经做过排查。9. 我的真实建议从今天开始用Docker的思维重新看待软件聊到最后我想说点实际的。Docker入门其实不难难的是思维方式的转换。我接触过不少开发者安装Docker时觉得很新奇但跑通MySQL之后又回到原来的工作流觉得容器和自己没什么关系。这种心态很可惜——因为当你开始用镜像打包不变、容器运行可抛、数据卷独立持久这套思维去看待部署很多以前头疼的问题会瞬间理顺。例如你本地开发时用Docker装一个Redis、一个PostgreSQL把它们当作测试环境的固定组成部分。代码怎么写无所谓跑起来的环境是一样的。团队成员只需要一条docker compose up -d就能在五分钟内得到一套和线上几乎相同的环境再也不用把我这能跑你那里不行挂在嘴边了。再比如你把应用做成镜像后推送一次镜像测试、预发、生产三套环境拉同一个镜像跑配合环境变量微调差异。版本不一样不存在了。环境漂移不存在了。我个人实际操作中的体会是刚开始可以先把最常用的一两个项目容器化比如MySQL、Redis日常使用中逐步切换不需要一次性把全部生产服务迁进来。等你真正经历过一次删掉容器、秒级重建、数据还在的流程你才会理解容器化的核心价值不在技术本身而在它让环境管理变成了一件标准化的、可重复的、不依赖某台具体机器的事。最后一个实用小技巧在你本地的Shell配置里加上alias dcdocker compose、alias dpsdocker ps --format table {{.Names}}\t{{.Status}}\t{{.Ports}}日常操作会顺手很多。有关Docker的问题欢迎带着你的具体报错和场景来聊光看报错不看上下文很多时候没法给你一个精准的建议。