Docker容器化实战:从镜像容器到MySQL、Redis与GitLab部署

发布时间:2026/9/15 11:26:56
Docker容器化实战:从镜像容器到MySQL、Redis与GitLab部署
这阵子好几个朋友问我同一个问题Docker到底怎么学网上教程一大堆命令也背了不少可一旦脱离教程自己动手人就懵了。同样卡住过的人应该都懂Docker最大的学习门槛不是命令而是“容器化”之后那些抽象概念镜像、容器、仓库、数据卷……当时我绕了不少弯路直到把整套东西翻译成“房地产开发”的流程一下就通了。镜像就是施工图纸容器就是从图纸上盖出来并且正在住人的房子Docker Hub是建材市场数据卷是小区仓库端口映射是门牌号。按这个逻辑后面每一步实操都有了坐标。这篇文章会沿着“拿地开发→业主入住→社区组网→物业上线→日常巡检”这条线带你把Docker环境装好把MySQL 8.0、Redis主从、GitLab都跑起来最后搞定日志膨胀和磁盘清理这些日常运维问题。不管你是刚接触容器的新手还是已经在用但经常“为什么跟教程不一样”的开发者应该都能找到对应的答案。1. 一个比喻拆掉概念门槛镜像、容器、仓库在房地产里各是什么角色1.1 镜像更像“施工图纸合集”不是可以直接拷走的硬盘文件很多新手最容易犯的错是把镜像理解成“一个可以复制的小型操作系统”。镜像确实包含操作系统、运行时环境、依赖库和应用代码但它是一个只读模板没法“开机”也没法直接登录。就好比你手里拿到的是一整套施工图纸图纸上有结构图、水电图、装修图纸张堆在一起像一本书但你不能住在图纸里。真正区别在这里镜像是分层的。每一层对应Dockerfile里的一条指令类似图纸里的“结构层”“防水层”“电路图”。Docker在构建时会做层缓存修改了后面的指令前面没变的层直接用缓存不用重新画楼。这也是为什么你改一个环境变量重新构建会快得多因为底层镜像层没变只有最上面几层在重做。理解这个后面写Dockerfile才知道怎么调优顺序把不常变的指令放前面把易变的代码放最后。1.2 容器是“正在住人的房子”一个图纸可以盖无数栋楼容器是从镜像创建的运行实例可以启动、停止、删除也可以进入里面执行命令。你把图纸交给施工队盖出来的房子就是容器。同一张图纸可以盖出无数栋一模一样的楼同理同一个镜像可以启动无数个互不影响的容器。举个例子你有一个Nginx镜像可以启动三个容器分别跑三个不同网站互不干扰。有人担心“容器删了是不是什么都没了”这里就要区分容器本身是可丢弃的但如果你把数据写在容器内部那确实会随容器删除一起消失如果你把数据放在挂载卷里就像把贵重物品寄存到小区仓库房子推倒重建东西还在。这也是后面MySQL部署的核心逻辑。1.3 仓库、数据卷、端口映射、网络各自的“小区定位”把这些配角一次说清楚后面实操就不会乱仓库RegistryDocker Hub就是全球最大的建材市场兼图纸库docker pull就是去建材市场进货。企业内部也可以自建私有仓库相当于只有业主才能进的内部建材中心。数据卷Volume相当于小区公共仓库。容器这个“房子”被拆了重建仓库里的东西不丢。常见用法是把宿主机目录挂载进容器相当于你把自己家里的保险柜直接做成小区物业的金库两头通着。端口映射-p参数宿主机相当于整个小区的门牌系统。容器内部有一扇门端口3306但这扇门在小区内部外界进不来。-p 3306:3306相当于在小区围墙上开一个口子外面的人从宿主机IP的3306进来就能找到容器里的3306。网络Network默认bridge网络相当于一条公共街道容器之间用IP找对方但IP经常变。自定义网络相当于新建一个独立社区内部配了自动通讯录直接用服务名互相访问不用记IP。概念就到这接下来是实操。说句实话概念理解的深度决定你后面排错的速度。很多“玄学问题”往上一套就通了。2. 拿地开工Windows和Linux安装Docker的实际操作与启动失败排查2.1 Ubuntu上安装Docker Engine两种方式按场景选如果你用的是Ubuntu服务器最省事的方式是直接apt install docker.io一条命令装完适合只想赶紧跑起来的场景。但问题也明显系统源里的Docker版本可能偏旧一些新语法和功能用不了。所以我更推荐用Docker官方apt仓库装社区版。先把依赖装好sudo apt-get update sudo apt-get install ca-certificates curl gnupg sudo install -m 0755 -d /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg sudo chmod ar /etc/apt/keyrings/docker.gpg然后添加仓库源。这里要注意$(. /etc/os-release echo $VERSION_CODENAME)会自动识别你的Ubuntu版本代号比如22.04是jammy不需要自己手动改echo \ deb [arch$(dpkg --print-architecture) signed-by/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \ $(. /etc/os-release echo $VERSION_CODENAME) stable | \ sudo tee /etc/apt/sources.list.d/docker.list /dev/null接着更新源并安装sudo apt-get update sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin推荐把docker-compose-plugin也一起装了因为后面安排GitLab这类多容器应用直接用docker compose命令比老式的docker-compose二进制更方便不用单独下载。装完做两件小事sudo systemctl enable --now docker sudo usermod -aG docker $USER第一行让Docker开机自启第二行把当前用户加进docker组以后不用每次敲sudo。注意执行完usermod后要重新登录终端才生效否则你会遇到一个非常经典的报错Got permission denied while trying to connect to the Docker daemon socket。这不是安装有问题就是当前会话还没拿到docker组权限别傻傻去重装。2.2 Windows装Docker Desktop那个“virtualization support not detected”的真正解法Windows上装Docker一般就是Docker Desktop默认走WSL 2后端。安装包不好找的时候我建议直接去Docker官网下载选对应系统的安装程序一路点下一步就能装上。但很多人卡在启动这一步弹窗报错Docker Desktop failed to start because virtualization support is not detected。我当年第一次看到这个错误第一反应是进BIOS开虚拟化折腾半天发现BIOS里早就开了问题根本不在那。这个报错是个“通用烟雾弹”它只告诉你“虚拟化相关的东西有问题”但具体是哪个环节坏了自己去查。按这个顺序排查基本能覆盖绝大部分情况任务管理器确认虚拟化状态CtrlShiftEsc打开任务管理器切换到“性能”页选CPU看右下角“虚拟化”是不是“已启用”。如果显示“已禁用”进BIOS开Intel VT-x或AMD SVM。确认Windows功能开启以管理员身份打开PowerShell执行dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart执行完必须重启电脑。设置WSL 2为默认版本并更新内核wsl --set-default-version 2 wsl --updatewsl --update这一条很多人会漏掉老内核和新的Docker Desktop后端不兼容启动时就给你报一个莫名其妙的状态。还有一个低调但很常见的坑如果你本机装了VMware、VirtualBox这类虚拟化软件它们和WSL 2争抢虚拟化资源Docker Desktop也可能启动失败。新版本兼容性好了很多但老机器上依然会打架实在不行只能二选一。Docker Desktop真正启动成功后记得去Settings里把资源限制调一下。默认内存占用很高老电脑直接卡到怀疑人生。我习惯把内存限制到4GB以内CPU看情况给一半反正日常跑容器完全够用。2.3 安装完成后的首次验证跑通hello-world和查看版本信息不管是Linux还是Windows装完都要做一次“竣工验收”。Linux终端执行sudo docker run hello-worldWindows上打开PowerShell或CMD执行docker version docker run hello-worlddocker version能同时显示服务端和客户端版本如果只有客户端信息而服务端报错说明daemon没起来回上一节排查。docker run hello-world会先检查本地有没有镜像没有就去仓库拉一个最小测试镜像跑完输出一段说明文字就退出。看到那句“Hello from Docker!”说明整套环境已经通了可以正式开始“建房”。3. 第一批业主入住MySQL 8.0容器化部署、数据持久化与编码坑3.1 docker run启动MySQL 8.0的参数逐个拆解MySQL可以说是容器化最常见的“第一个生产级应用”。拉镜像然后启动docker pull mysql:8.0接着创建两个宿主机目录一个放数据文件一个放自定义配置sudo mkdir -p /data/mysql /data/mysql-conf然后启动容器docker run -d \ --name mysql8 \ --restartalways \ -p 3306:3306 \ -e TZAsia/Shanghai \ -e MYSQL_ROOT_PASSWORD你的密码 \ -v /data/mysql:/var/lib/mysql \ -v /data/mysql-conf:/etc/mysql/conf.d \ mysql:8.0逐个说清楚这些参数因为它们代表一类通用逻辑理解了MySQL换PostgreSQL、Redis都一样-d后台运行终端不霸屏。--name mysql8给容器起名字后面所有操作都用名字代替容器ID省得记一长串哈希。--restartalways容器挂了自动重启机器重启也自动拉起。相当于给房子上了“物业托管”异常退出不用你手动去救。-p 3306:3306把宿主机的3306映射到容器的3306。注意左边是宿主机端口右边是容器内端口想用其他端口改成-p 3307:3306就行。-e TZAsia/Shanghai设置容器内时区。MySQL的NOW()函数会读系统时区不设的话默认UTC你查出来的时间比北京时间慢8小时又是个“神秘Bug”。-e MYSQL_ROOT_PASSWORD你的密码初始化时设置root密码。这是官方镜像规定要提供的环境变量第一次启动容器时才会生效。-v /data/mysql:/var/lib/mysql把宿主机目录挂载到容器内MySQL的数据目录数据全落在这里容器删了数据还在。-v /data/mysql-conf:/etc/mysql/conf.d挂载自定义配置目录。放在conf.d下的*.cnf文件会被MySQL自动读取不需要手动改主配置文件。3.2 数据持久化验证容器删掉重建数据还在吗很多人对“数据卷”这个概念半信半疑总觉得数据写在容器里天经地义。我建议你亲手做一次实验比看十篇文档都管用。先在MySQL里建一个测试库和表CREATE DATABASE testdb; USE testdb; CREATE TABLE student ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) ); INSERT INTO student(name) VALUES (张三);然后把容器删了docker stop mysql8 docker rm mysql8字面看你辛辛苦苦建的库表应该都没了。但用同样命令重新启动一个新容器docker run -d \ --name mysql8 \ --restartalways \ -p 3306:3306 \ -e TZAsia/Shanghai \ -e MYSQL_ROOT_PASSWORD你的密码 \ -v /data/mysql:/var/lib/mysql \ -v /data/mysql-conf:/etc/mysql/conf.d \ mysql:8.0重新连上去查数据还在。这就是卷的价值容器是可再生的数据目录是独立于容器生命周期的。生产环境里任何无状态的服务你都可以随便重启重建但数据库、文件存储这类有状态服务必须用卷把数据“挪到容器外面”。有个细节要提醒宿主机挂载目录的权限。如果挂载后MySQL启动失败先去docker logs mysql8看看是不是Permission denied。宿主机目录的属主和容器内mysql用户ID不一致时经常会出这种问题简单解法是把宿主机目录属主改成和容器一致的UID不要一上来就chmod 777搞得太野后面日志和权限会很难收场。3.3 从外部连接MySQL以及“caching_sha2_password”这个老熟人的坑容器起来了接下来就是从宿主机或者其他机器连进去。宿主机直接mysql -h127.0.0.1 -P3306 -uroot -p如果提示找不到mysql命令说明宿主机没装客户端可以用容器内的客户端docker exec -it mysql8 mysql -uroot -p这里要专门讲一个MySQL 8.0的高频报错连接时提示Authentication plugin caching_sha2_password cannot be loaded。MySQL 8.0默认认证插件改成了caching_sha2_password但你本地的老客户端、或者某个老应用用的还是老的mysql_native_password协议两边对不上就报这个错。解决思路有两个方向。一是升级客户端新客户端都兼容默认插件二是如果应用代码不方便动就在MySQL里给应用单独建一个用老插件的账号CREATE USER app% IDENTIFIED WITH mysql_native_password BY app密码; GRANT ALL PRIVILEGES ON testdb.* TO app%; FLUSH PRIVILEGES;注意这里%表示允许从任何主机连接。生产环境建议收敛成具体IP网段别学我这样图省事。再补一个关于编码的真实经验MySQL 8.0默认字符集已经是utf8mb4比以前版本的frown相比省心了不少。但如果你还是遇到中文乱码先执行SHOW VARIABLES LIKE character%;重点看character_set_server和character_set_database这两项。如果服务器端不是utf8mb4就在挂载的配置目录里新建一个my.cnf[mysqld] character-set-serverutf8mb4 collation-serverutf8mb4_unicode_ci然后重启容器docker restart mysql8我见过太多人把时间浪费在“改表的字符集”“改连接串”上其实改完服务端默认字符集再把表的字符集对齐一次就解决了。4. 组个高可用社区Redis主从复制在容器网络里的完整编排4.1 为什么在容器里搭Redis主从关键在“自定义网络”Redis主从复制在传统服务器上配置不算难但放到容器里有个配置项会让人卡很久从节点连接主节点时填什么地址如果你用默认bridge网络容器之间靠IP互相访问但容器每次重建IP都可能变化写死在配置文件里就是埋雷。自定义网络的作用就是专门解决这个问题的——同一网络下的容器可以直接用容器名当主机名访问。相当于整个小区每栋楼都挂了一块印着户主名字的门牌不用记门牌号。创建网络docker network create redis-net这个网络就是一个内部小区之后启动的主从节点都连到这个网络里容器之间自动可以按名字找到对方。还有一个容易忽略的点容器网络模式默认bridge时端口映射依然有效但容器之间的内部通信走的是网络里的虚拟通道不经过宿主机端口所以replicaof填的地址是容器名加容器端口。4.2 一键拉起主从两个节点并验证同步状态启动主节点docker run -d \ --name redis-master \ --network redis-net \ -p 6379:6379 \ -v /data/redis-master:/data \ redis:7 \ redis-server --requirepass 123456 --appendonly yes启动从节点docker run -d \ --name redis-slave \ --network redis-net \ -p 6380:6379 \ -v /data/redis-slave:/data \ redis:7 \ redis-server \ --replicaof redis-master 6379 \ --masterauth 123456 \ --requirepass 123456 \ --appendonly yes这些参数要看懂否则出事不知道怎么排--replicaof redis-master 6379指定主节点地址用的是容器名而不是IP。这是自定义网络起的作用。--masterauth 123456从节点同步时会带上这个密码去认证主节点。只要主节点设置了requirepass这项必须配否则从节点日志里会刷MASTER aborted replication with error: ERR Client sent AUTH, but no password is set。--requirepass 123456客户端访问本实例要认证。主从两个实例都配了相同密码后续读写方便。--appendonly yes开启AOF持久化配合挂载目录Redis重启后数据不丢。验证主从是否建立。先看主节点docker exec -it redis-master redis-cli -a 123456 info replication重点看输出里这几行role:master connected_slaves:1 slave0:ip172.x.x.x,port6379,stateonlineconnected_slaves:1说明已经从节点挂上来了。再看从节点docker exec -it redis-slave redis-cli -a 123456 info replication输出里role:slavemaster_link_status:up就对了。然后实际测试同步docker exec -it redis-master redis-cli -a 123456 set site container-works docker exec -it redis-slave redis-cli -a 123456 get site从节点能返回container-works说明主从链路完全通了。整个房子的数据到了一栋楼自动复制一份到隔壁楼这就是高可用的起点。4.3 我踩过的坑从节点起不来、日志刷认证错误、密码明文警告这套Redis主从配置我前前后后搭过好几次有几个坑记忆特别深。第一个坑是网络没选对。如果启动从节点时忘了加--network redis-net它跑在默认bridge网络里replicaof redis-master 6379根本解析不到这个名字从节点日志会一直报Could not connect to Redis at redis-master:6379。这不是密码问题也不是防火墙问题是容器之间压根不在同一个小区里互相找不到门。第二个坑是firewalld/iptables拦截。宿主机上装了防火墙时可能拦住容器之间跨网络的通信或者拦住外部访问。排查手段是先在容器内部互相pingdocker exec -it redis-slave ping redis-master能通再往后查不通就先看网络和防火墙。这个思路适合所有容器间通信问题先确认网络通不通再看认证和应用配置。第三个坑不是核心功能但很影响操作体验用redis-cli -a 密码时终端会提示Warning: Using a password with -a is not safe因为密码会出现在shell历史记录里。测试环境无所谓生产建议用REDISCLI_AUTH环境变量或者干脆交互式输入密码。细节虽小但公司里面安全意识越来越强这个习惯最好早点养起来。5. 物业管理处上线用Docker Compose一次拉起GitLab和多容器应用5.1 Compose文件长什么样从“一栋楼”到“整个社区的规划图”前面几个例子都是docker run一条命令启动一个容器类似在一栋楼一栋楼地造房子。但真实业务里一个应用往往由好几个服务组成前端、后端、数据库、缓存、消息队列……一个个手动启动指令又长又容易漏参数而且服务之间的启动顺序、网络依赖都要自己维护这就该Docker Compose出场了。Compose的核心就是一个YAML文件把整个项目需要的所有容器、网络、卷都定义在里面执行docker compose up -d就自动按配置创建。它就是整个小区的规划设计图哪栋楼在哪、什么高度、怎么排管网、物业怎么值班全写在一张图上。一个典型的Compose文件长这样version: 3.8 services: web: image: nginx:latest ports: - 8080:80 volumes: - ./html:/usr/share/nginx/html networks: - app-net db: image: mysql:8.0 environment: MYSQL_ROOT_PASSWORD: 你的密码 volumes: - db-data:/var/lib/mysql networks: - app-net volumes: db-data: networks: app-net:这里有个关键概念要明白Compose会自动创建一个默认网络所有service都在这个网络里直接用service名互相访问。比如web容器里要连接数据库配置里的数据库地址直接写db:3306不用写IP。这跟前面Redis自定义网络的原理一模一样只是Compose替你做了。5.2 实际部署GitLab时需要注意的资源、SSH端口和shm_sizeGitLab是容器化里很典型的一个“重应用”官方镜像集成了完整的一整套Nginx、PostgreSQL、Redis、Sidekiq、Gitaly……全装在一个容器里。好处是部署简单坏处是吃资源。官方建议内存4GB起步我实测低于4GB在克隆和CI的时候特别容易OOM小内存机器跑起来会比较吃力。部署前先建好数据目录sudo mkdir -p /data/gitlab/config /data/gitlab/logs /data/gitlab/data然后写/data/gitlab/docker-compose.ymlversion: 3.8 services: gitlab: image: gitlab/gitlab-ce:latest container_name: gitlab restart: always hostname: gitlab.example.com environment: GITLAB_OMNIBUS_CONFIG: | external_url http://gitlab.example.com ports: - 80:80 - 443:443 - 2222:22 volumes: - /data/gitlab/config:/etc/gitlab - /data/gitlab/logs:/var/log/gitlab - /data/gitlab/data:/var/opt/gitlab shm_size: 256m启动cd /data/gitlab sudo docker compose up -d这里有几个点等你在生产环境吃过亏就明白为什么我特意标出来第一个是shm_size: 256m。GitLab内部组件会疯狂使用/tmp和共享内存默认64MB很容易导致shm get failed或者Git操作报“file too large”之类的怪错。把这个值提到256MB以上能省掉很多莫名其妙的运维工单。第二个是SSH端口映射。宿主机22端口往往已经被系统SSH占用了所以这里把GitLab的SSH端口映射成2222:22。之后项目里的clone地址要写成ssh://gitgitlab.example.com:2222/group/project.git很多人在这个上面栽跟头界面里显示的clone地址是gitgitlab.example.com:group/project.git点复制直接粘贴连上去必然失败因为默认走了22端口而宿主机22端口跑的是系统SSH不是GitLab的。需要在项目URL里手动加端口号。第三个是初始化时间。docker compose up -d执行完GitLab并不会马上就能访问它内部要初始化数据库、编译静态资源第一次启动可能需要3到5分钟中间还会自动重启几次。不要一看到docker ps里状态是starting就开始怀疑装坏了。观察进度用docker logs -f gitlab等日志稳定输出Starting gitlab或者直接访问http://gitlab.example.com看到登录页才算真正就绪。5.3 从Compose出发webvirtcloud这类平台型工具怎么容器化迁移聊完GitLab顺便说说现在网上热度不低的webvirtcloud这类平台型工具的容器化部署思路。很多人一看到“平台型应用”就头大觉得依赖多、配置杂不知道怎么下手。其实只要抓住三条线索大多数应用都能扒出来怎么容器化第一找官方是否已经发布了Docker镜像或Compose配置。现在主流开源平台基本都会给比如webvirtcloud、dify这类项目官方仓库里一般有Dockerfile或者docker-compose.yml。有就直接拉仓库里的compose文件改几个端口和目录docker compose up -d跑起来。第二没有官方镜像就自己写Dockerfile。写之前先回答三个问题这个服务的启动命令是什么配置文件放在哪数据写在哪启动命令决定了Entrypoint配置和数据目录决定了卷怎么挂。把这三件事梳理清楚一个基础Dockerfile很快就能出来。第三厘清服务依赖。平台型应用通常有Web前端、后端API、数据库、队列多个进程。优先考虑拆成多容器每个容器只干一件事再用Compose编排。拆不开的才考虑在一个容器里跑多个进程但要用supervisor之类做进程管理这属于最后手段。看到这里你会发现前面MySQL、Redis、GitLab这些实践看似各自独立其实最后都汇到了一条线上会写docker run就会写Compose会用数据卷就会设计持久化懂容器网络就能把多服务串起来。这也是我为什么坚持用“房地产开发”这条线讲容器化——真正的能力不是记住某个命令而是面对一个新项目时能快速画出它的“图纸”。6. 小区日常巡检容器生命周期管理、日志膨胀和磁盘清理手册6.1 常用命令的一句话记忆法容器跑起来之后日常运维就是一套固定动作。命令不少但按“看状态→看日志→进容器→看资源”这个顺序串起来很好记docker ps看现在哪些容器在运行加-a连已经退出的也一起看。docker logs -f 容器名实时看日志。想只看最后100行用docker logs --tail 100 容器名。docker exec -it 容器名 bash进容器内部。相当于你走进房子里面检查水电。docker inspect 容器名看容器的详细信息IP、挂载目录、环境变量全在里面JSON格式可以配合grep用。docker stats实时看所有容器的CPU、内存、网络占用像物业的能耗监控大屏。docker system df看整个Docker占了多少磁盘哪个类型占了大头。这些命令不算多关键是养成习惯遇到问题先docker logs看日志的时候别只看最后几行结合docker ps -a看容器状态变化很多故障在日志里其实已经明确告诉你了只是你之前没耐心翻。6.2 容器日志为什么越来越大怎么限制体积跑一段时间后你可能会发现系统磁盘空间悄悄变小。最常见的元凶就是容器日志。Docker默认把所有容器的标准输出和标准错误都记到本机格式是JSON文件默认不限制大小。一个日志输出频繁的容器几天就能吃掉几个GB磁盘。我遇到过一个真实案例某个应用因为一条异常被死循环打日志整个磁盘被撑满最后数据库都起不来了。所以新环境我第一件事就是改Docker的全局日志配置。编辑/etc/docker/daemon.json{ log-driver: json-file, log-opts: { max-size: 10m, max-file: 3 } }然后重启Dockersudo systemctl restart docker这里有个特别容易忽略的细节这个配置只对新创建的容器生效已经存在的容器不会自动应用新配置。所以你改完配置之后要把相关容器重建一下用docker compose up -d重新拉起才能让日志限制真正落地。如果日志已经吃掉了大量磁盘可以手动清空指定容器的日志文件sudo truncate -s 0 $(docker inspect --format{{.LogPath}} 容器名)这条命令直接把容器日志文件截断为零而不是删除文件容器还在正常写不用重启。生产环境执行前先确认这个容器的日志确实不重要。6.3 一套顺手的一键清理流程和自动巡检脚本最后分享一套我自己的日常清理流程。不用天天做一周一次就够。手动清理的话按这个顺序docker ps -a --filter statusexited | grep -v NAMES先看有哪些已经退出的僵尸容器确认没用就删掉docker system prune -f这个命令会清理所有已停止的容器、没有被使用的网络、挂起状态的镜像以及构建缓存。强烈建议不要直接加--volumes因为prune一旦加了--volumes会顺手删掉所有没被容器引用的数据卷而有些卷里可能躺着你的备份数据删了就真的没了。加-a会连所有没有被容器引用的镜像都删掉。这个要用得谨慎因为当前没被引用不代表以后不需要删了就得重新docker pull。我习惯只加一个过期时间参数让它只清理超过一周的构建缓存docker system prune -f --filter until168h把这条命令写成一个cron任务每天早上自动跑0 4 * * * /usr/bin/docker system prune -f --filter until168h /var/log/docker-prune.log 21顺带把前面配好的日志限制和每日清理脚本配合起来磁盘失控的概率就非常低了。我自己的服务器这样跑了两年多再没出现过“磁盘满了导致服务挂掉”的深夜紧急电话。最后说一个习惯任何生产环境的容器启动命令里最好都带上--restartalways跑起来之后再每天看一眼docker stats里有没有哪个容器内存异常飙升。容器化这件事从把第一个MySQL跑起来到把整套服务体系放进Compose统一编排其实就是在不断积累这些细节。把每一个报错都当成深入了解底层逻辑的机会一段时间后你自然会形成条件反射看到现象就能猜到根因。到了那个阶段Docker对你来说就不再是“又一个要学的工具”而是真正属于自己的小区所有设施都在你有条不紊的掌控之中。