1. 镜像和容器,说白了就是一张图纸和无数栋楼的关系
我见过太多刚接触 Docker 的人卡在同一个地方:明明已经docker pull拉了一个镜像,也照着教程docker run跑起来了,可真要他自己解释一句“镜像和容器到底有什么区别”,又说不清楚。最典型的表现就是——在容器里改了文件,然后到处问“为什么我的镜像没变?”“容器删了数据还在不在?”“我能不能把一个容器直接拷给同事?”这些问题归根到底,就是没把镜像和容器的关系吃透。
先说最直观的理解方式:镜像是一张建筑施工图纸,容器是照着图纸盖出来的那一栋楼。图纸不因任何一栋楼而被改变,同一张图纸可以盖出无数栋结构相同但独立存在的楼;容器就是那个“独立存在”的运行实例,它有自己的状态,有自己的门窗、家具、住户,但这些全都建立在图纸规定的框架之上。
很多热词里提到的“linux镜像安装”“ubuntu官网镜像下载”“win11镜像下载”,其实是操作系统安装包 ISO,和 Docker 镜像是两码事,后面我会专门讲。先记住这句话:容器是由镜像创建的运行实例,镜像是容器启动时的只读模板。
我平时排查问题时,发现绝大多数概念混淆都源于三个根本点没理清:镜像只读、容器可写、一个镜像可以起多个容器。这三个点只要心里有数,后面的所有操作都不会乱。
1.1 一个镜像能起多少个容器?理论上没有上限
同一份 MySQL 8.0 镜像,你可以在一台服务器上起三个容器:3306、3307、3308 三个端口各一个,它们之间互不干扰,各自拥有独立的数据文件、独立配置文件、独立日志。这在生产环境里很常见,比如同一个镜像要分别部署到测试、预发、正式环境,或者要给不同租户做数据库实例隔离,都是“一套镜像、多套容器”的玩法。
之所以能做到这一点,得从文件系统层面理解。每个容器启动时,Docker 会在镜像的基础上再叠加一层“容器层”,这层是空的、可写的。容器对文件的所有增删改,都只会落在这个容器层里,镜像本身始终是那个只读的“底稿”。这就像你在同一张设计图上复印了十份,每份上面都可以用铅笔随意涂改,但原件永远是干净的。
也正是这个机制,让“容器多了会不会占很大磁盘空间”这个问题有了答案:不会。多个容器共享同一个镜像的只读层,磁盘上只存一份镜像数据,每个容器额外占用的只是自己那个可写层的容量。我之前见过有人担心十个容器会把磁盘撑爆,实际算下来,十个容器加一起的额外开销可能都不到几十 MB,前提是你没有往里面写大文件。
1.2 核心区别只有四个字:只读与可写
我用一张表把这俩的核心差异列清楚,新手照着记,基本就不会混淆了:
| 维度 | 镜像(Image) | 容器(Container) |
|---|---|---|
| 本质 | 静态的只读模板 | 镜像运行后的动态实例 |
| 文件系统 | 只读层,不可直接修改 | 在镜像上叠加可写层,可自由修改 |
| 生命周期 | 不随容器启停而改变,长期存在 | 随 start/stop/rm 而启停或消亡 |
| 占用的资源 | 只占磁盘空间 | 占用 CPU、内存、网络、磁盘 |
| 同一个镜像 | 一份 | 可变出多个互不干扰的实例 |
| 操作命令 | pull/build/push/tag | run/start/stop/rm/exec |
| 能不能直接改名 | 可以打 tag | 通过docker rename改名 |
这张表你不需要背,但心里一定要有。特别是“镜像不可直接修改”这条,很多人刚学的时候会在容器里装了一堆软件后跑到镜像目录去找文件,发现找不到,然后又跑回来问我“是不是装错地方了”。不是的,你装的东西都在容器的可写层里,镜像目录里当然没有。
1.3 别把“Docker镜像”和“系统ISO镜像”混为一谈
热搜词里出现了“linux镜像安装”“ubuntu官网镜像下载”“win11镜像下载”“centos7镜像下载官网”这些。这些说的是操作系统安装包,一张 Windows 11 的 ISO、一个 Ubuntu 的安装光盘镜像,确实是“镜像”二字,但和 Docker 镜像完全是两个物种。
操作系统 ISO 是用来装物理机或虚拟机的,它包含完整的引导程序、内核、驱动、安装器。Docker 镜像则是用来“生成容器”的,它本质上不是一个完整的操作系统,只是一套完整的应用运行环境。一个 CentOS 的 Docker 镜像大约只有二百来 MB,而 CentOS 的完整 ISO 要好几个 GB,就是因为 Docker 镜像里没有内核、没有引导程序,它借用的是宿主机内核,只是把用户态的文件系统、库、应用打完包。
这意味着一个很反直觉的事实:你可以在 Windows 的 Docker Desktop 里跑一个 Ubuntu 容器,但这个容器用的内核其实是 Windows 自带的 WSL2 里的 Linux 内核。你跟别人说“我在 Windows 上跑了个 Ubuntu”,严格来说,你跑的是 Ubuntu 的用户态环境,而不是一套完整的 Ubuntu 操作系统。这个概念不搞清楚,后面看容器的资源限制、安全隔离都会觉得别扭。
2. 镜像为什么天生是分层的?这是容器能飞快启动的根基
镜像和容器在物理上的关系已经说清了,接下来得回答一个更底层的问题:镜像内部到底是什么结构,才让“复制出无数容器且相互独立”这件事这么便宜、这么快。
很多教材喜欢直接抛“镜像是由多层只读层叠加而成的”,但没有解释为什么要有层。我打个比方:盖楼的时候,地基是一层,结构是一层,外墙是一层,装修是一层。如果每次交付一位业主都要从地基开始重新建一栋一模一样的楼,那代价不可想象。Docker 的思路是,这些层全部共享,新业主只需要在自己的户型层上做点缀就行。
2.1 联合文件系统:多层目录拼成一个完整视图
Docker 镜像的分层,底层靠的是联合文件系统(UnionFS),现在主流宿主机上默认用的是 OverlayFS。它的核心能力是:把多个不同的目录“叠放”在一起,对外暴露成一个统一的文件系统视图。
你写一个 Dockerfile,里面通常有好几条指令,比如:
FROM centos:7 RUN yum install -y net-tools COPY app.jar /app/app.jar CMD ["java", "-jar", "/app/app.jar"]每一条RUN、COPY、ADD指令,都会生成一个新的只读层。第一层是基础镜像centos:7的全部文件;第二层记录的是“安装 net-tools 之后,文件系统相比第一层多出来的那些文件”;第三层记录的是“拷贝 app.jar 之后新增的文件”。每一层只保存增量。
多个只读层叠加后,从容器里看是一个完整的文件系统:既有 CentOS 的基础目录,又有 net-tools,又有 app.jar。但每一层各自独立存放,互不影响。这也解释了为什么修改容器里的文件那么“轻”:
- 读文件时,Docker 从上往下逐层查找;
- 写文件时,Docker 不会去改底层只读文件,而是在最上层的可写层新建一个副本,然后读写这个副本。
2.2 写时复制:不复制就永远不会慢
“写时复制”这个概念,很多学过 Linux 或学过程序员内存管理的人会觉得眼熟,它在 Docker 里也一样关键。镜像的只读层是可读的,但一旦容器要修改某一个底层文件,Docker 会将这个文件从只读层复制一份到可写层,然后对副本做修改。底层文件始终保持原样。
所以,如果容器只是读文件、跑程序,它几乎不增加额外存储开销。只有真正写操作发生,才会复制文件到可写层。这也是为什么同时启动几十个容器,不会像你想象中那样瞬间耗尽磁盘——绝大多数场景下它们只是在“读同一本原件”。
不过要提醒一句:写时复制节省的是“时间”和“空间”,但如果你在容器里写大量频繁变动的文件(比如直接把 MySQL 的数据文件放在容器可写层),那性能会比挂载数据卷差一些,后续我会在 MySQL 实战部分细说。
2.3 容器删除,可写层跟着消失
既然可写层是容器独有的,那容器的生命周期就决定了数据的安全性。docker rm删除容器时,那个可写层也会一并被删除,里面所有“容器运行期间写入的数据”就没了。
这句话说出来很多新手觉得“这不是废话吗”,但实际踩坑的人前赴后继。最典型的就是用 Docker 跑 MySQL 时不挂载数据卷,跑了两个月,数据库里几十 GB 业务数据,某天误操作docker rm一下,数据直接清零。镜像还在,容器还能重新起,BUT 数据库里的一切都没了。
正确做法在前面已经埋了伏笔:把“容器可写层里需要持久化的数据”放到宿主机目录或数据卷里,通过-v参数把宿主机目录挂载进去。这样容器删了重起,数据仍然在宿主机的挂载目录里。判断一个东西该不该放容器里的标准很简单:删掉这个容器,你会不会心疼?会心疼,就别放可写层,放数据卷。这句话我几乎在每次答疑里都要重复一遍。
3. 镜像与容器不是单向关系:build、commit、import/export 全链路
理解了“镜像造容器”之后,就该看反方向的操作了:如何从容器里产出新的镜像,以及镜像文件如何在机器之间搬运。这一步是很多人从“会用”过渡到“会造”的关键。
3.1 Dockerfile 是“图纸的图纸”,最推荐的正规路
前面提过,镜像是多层只读层拼接出来的。怎么拼?最标准的方式是写 Dockerfile,然后用docker build去构建。
docker build -t myapp:v1 .这里的“.”指的是构建上下文目录,Docker 会把该目录下的所有文件打包传给守护进程,用于后续构建。一个常见坑是:在项目目录里docker build,但目录里有个超大无关文件夹没排除,构建过程就奇慢无比。解决办法是写.dockerignore文件,把node_modules、.git、target这些排除掉,有点像.gitignore的作用。
在 Dockerfile 里,层级划分直接影响镜像体积。经验法则是:
- 把变化频率小的指令放前面,变化频率大的放后面,充分利用层缓存;
- 多个
RUN尽量通过&&合并,减少不必要的层; - 安装完软件包后及时清理缓存文件,比如
yum clean all、rm -rf /var/cache/apt/*。
这样做出来的镜像更小、构建更快、复用性更好。Dockerfile 构建出来的镜像天然具有“可复现”特性:任何人拿到同一份 Dockerfile,构建出的镜像基本一致。这对交付和协作特别重要。
3.2 docker commit:应急可以,别当作主力
相比 Dockerfile 的“正规军”,docker commit更像是“战地急救包”。它的作用是:当前容器的可写层 + 底层只读层,打包成一个新镜像。
docker commit my_container myapp:hotfix-20250301什么时候用它?比如你在容器里手动调试了半天,终于调通了某个配置,想快速把状态保存下来,避免容器重起后配置丢失。又或者你在容器里临时装了几个包,来不及写 Dockerfile,想先打包一份镜像出去应急,docker commit都够用。
但我一直不建议把 commit 作为主力镜像生产方式,原因有两个:
- 不可复现:镜像怎么产生的、当时装了什么、改了什么,全在容器状态里,历史完全不可追溯。一旦镜像丢了,光靠 commit 产物很难还原过程。
- 镜像体积容易膨胀:容器可写层往往带着大量临时文件、日志、包管理缓存,commit 会把这些全部纳入镜像,越积越脏。
所以我的习惯是:可以用 commit 做“现场快照”,但随后必须根据现场操作反推出一份 Dockerfile,用正经方式重新构建一版干净镜像。
3.3 save/load 与 export/import,别再用错
这两组命令都用来迁移镜像,但效果完全不同,这里值得单拎出来说。
| 命令 | 作用对象 | 保留镜像层结构和历史吗 | 典型使用场景 |
|---|---|---|---|
docker save/docker load | 镜像 | 保留全部层和历史 | 同架构机器之间完整迁移镜像,或离线分发 |
docker export/docker import | 容器文件系统 | 不保留层,只有文件系统快照 | 只看容器文件内容、交给其他工具处理 |
docker save导出的是一个压缩包,里面包含这个镜像的所有只读层记录和元数据;docker load可以原样恢复成一个可用镜像,docker run没问题。docker export导出的则是容器当前整个文件系统的扁平快照,它已经没有“镜像”的结构了,docker import导入后得到的镜像,层历史完全丢失,体积也往往很大。
简单记:要“完整镜像搬家”用 save/load;只是“把容器文件打包带走”用 export/import。经常有人把这两个混着用,结果 load 的时候报格式错误或者镜像跑不起来,问题大多出在这。
4. 镜像与容器关系实战:MySQL、Redis 主从、青龙面板挨个走一遍
概念讲再多,不如上手跑几个场景。我挑的三个例子不是随意选的,它们分别代表了镜像与容器关系里三个最典型的应用面:数据持久化、多容器协同、镜像升级带来的依赖问题。
4.1 MySQL 8.0:不挂数据卷的容器都活不过一次 rm
先用 MySQL 8.0 演示最基本的“镜像 + 数据卷 + 端口映射”组合。我推荐的启动命令长这样:
docker run -d \ --name mysql8 \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORD=你的密码 \ -v /opt/mysql8/data:/var/lib/mysql \ -v /opt/mysql8/conf:/etc/mysql/conf.d \ --restart=always \ mysql:8.0重点看-v两个挂载。把容器里 MySQL 的数据目录/var/lib/mysql挂到宿主机/opt/mysql8/data,这样无论容器怎么删、怎么重起,数据文件都留在宿主机磁盘上。配置文件也同理,你可以直接在宿主机上改my.cnf,重启容器即可生效,不用进容器里折腾。
我见过太多“docker 安装 mysql 失败”的求助帖,最后发现十有八九是这三类问题:
- 没挂数据卷,容器一删数据全没,追悔莫及;
- 端口映射冲突,3306 被宿主机已有 MySQL 占用了,容器起不来;
- 字符集没配,进去后中文全变问号。
第一条最致命。哪怕你只是本地测试,也养成“关键数据一律挂卷”的习惯。
4.2 Redis 主从:一份镜像,三个容器,构成一套集群
Redis 主从是“同一镜像起多个容器”的绝佳演示。主从关系不是镜像之间的区别,而是容器启动参数和配置的区别。
# 主节点 docker run -d --name redis-master -p 6379:6379 redis:7 redis-server --appendonly yes # 从节点1 docker run -d --name redis-slave1 -p 6380:6379 redis:7 redis-server --slaveof 目标IP 6379 # 从节点2 docker run -d --name redis-slave2 -p 6381:6379 redis:7 redis-server --slaveof 目标IP 6379三个容器,用的是同一个redis:7镜像,但各自有不同的容器名、不同端口、不同启动参数。容器名和端口就是“同一张图纸盖出的三栋楼的门牌号和地址”。
这里面最容易踩的坑是:主从之间用localhost通信。在容器环境下,每个容器都认为自己是localhost,redis-slave1 里的localhost指向的是它自己,永远连不上主节点。必须写宿主机的局域网 IP,或者通过--network让容器处于同一自定义网络里,用容器名互相访问,比如--slaveof redis-master 6379。这个坑理解起来特别简单:三栋楼虽然是照着同一张图纸盖的,但每栋楼的“一楼门厅”都是各管各的,没有哪栋楼的“一楼门厅”能代表整个小区。
4.3 青龙面板与依赖管理:容器升级后,镜像层面的“搬家”问题
青龙面板这类应用,很多人用 Docker 跑,但升级镜像时经常会遇到一个经典问题:升级后依赖丢失,脚本跑不了。
原因拆开看就很清楚:青龙的 JS/Python 依赖安装在容器的可写层里。你docker pull了新版本的青龙镜像,然后用新镜像重新创建容器,新容器基于新镜像的只读层,完全没有你旧容器里手动安装的那些依赖,看起来就是“升级后依赖全没了”。
这是“镜像和容器生命周期”在真实世界里最直白的体现。依赖装在哪一层,决定了它会不会随着容器删除而消失。解决办法无非两个方向:
- 依赖固化进镜像:把依赖安装写进 Dockerfile,通过自定义 Dockerfile 重新构建镜像,这样新容器基于新镜像启动时,依赖天然就在只读层里;
- 依赖放在挂载卷里:把依赖目录通过
-v挂出来,新容器挂载同一个宿主机目录,依赖就不会因容器替换而丢失。
我更推荐前者,因为镜像本身可复现,团队里任何人拉下来都是同一套环境。如果你的场景是“必须用现成镜像”,那就至少要把依赖目录挂卷出来。总原则还是那句:会心疼的数据,就不要放在容器可写层里。
5. 容器和镜像“分家”之后,我遇到过的几个高发故障
这节聊的全是我自己或身边同事在实际环境里真踩过的坑,按出现频率排序。每一个坑,如果你把“镜像只读、容器可写、容器共享镜像”这组关系想清楚,基本都能自己推出答案。
5.1 Docker Desktop 起不来:virtualisation support 未检测到
很多人在 Windows 上装 Docker Desktop,启动时看到:
Docker Desktop failed to start because virtualisation support wasn't detected第一反应是怀疑 Docker 有问题,但 Docker 只是“报告问题的人”。它的意思是:容器需要 Linux 内核级的虚拟化支撑,而你的 Windows 环境把虚拟化关掉了。
排查链路一般是:
- 进 BIOS/UEFI,确认 Intel VT-x 或 AMD-V 已开启;
- 在 Windows 功能里,确认“适用于 Linux 的 Windows 子系统”和“虚拟机平台”两个选项已勾选;
- 如果之前装过旧版 Hyper-V,或者系统曾处于开发者模式,可能需要重启后再试。
Docker Desktop 在 Windows 上依赖 WSL2 或 Hyper-V 提供内核,本质上就是给容器提供“盖那栋楼的地基”。地基没打,图纸再完美,楼也立不起来。
5.2 容器内置 CentOS 7.9,sshd 启动失败
有人在做“容器内跑 sshd”时踩坑,现象是systemctl start sshd或直接启动sshd总失败。
这里要往深处想一层:很多官方镜像默认没有 systemd,甚至没有 init 进程管理。在容器里运行systemctl,大概率报错“System has not been booted with systemd as init system (PID 1)”。这不是 sshd 本身坏了,而是你错误地把“物理机/虚拟机习惯”直接搬进容器。
处理办法有两个方向:
- 不依赖 systemd,直接手工执行
sshd或者用service sshd start,前提是镜像装了对应的服务脚本; - 如果一定要 systemd 管理多个服务,那就选用带 systemd 的基础镜像或者在启动容器时额外配置特权模式,但这会让镜像变大、隔离性变差,一般不建议。
我的建议是:在容器里不要按“多服务常驻”的思路设计。一个容器装一个主进程,是 Docker 生态的主流用法。你如果必须同时跑 sshd + 主应用,要么用docker exec进容器调试而不是开 sshd,要么考虑拆分容器。镜像和容器的设计意图,本来就是让“一栋楼一层一个功能”,而不是把整栋楼搞成一个大杂烩。
5.3 宿主机访问不到容器内的 MySQL
容器起来了,docker ps正常,但宿主机上mysql -h 127.0.0.1 -P 3306连不上。排查思路分几步:
- 看端口映射:
docker ps里应有0.0.0.0:3306->3306/tcp,如果没有-p 3306:3306就没暴露,宿主机自然访问不了; - 看容器状态:
docker logs mysql8里有没有“ready for connections”的日志; - 看容器 IP 模式:如果容器用了
host网络模式,端口直接就打在宿主机监听上了,不再需要-p映射; - 最后看 MySQL 的 bind-address,默认对容器外部连接有时只监听 127.0.0.1,需要调整配置。
这类问题真正难的不是某一步,而是新手上路时常常在“容器里看得到,宿主机看不到”这道坎上绕圈子。本质上,容器是有自己网络命名空间的,宿主机端口和容器端口是两个世界,-p就是在两个世界之间开一扇门。
5.4 宝塔面板里某个容器要使用宿主机的网络环境
有网友问“宝塔内某个容器让他使用宿主机的网络环境”,其实说的就是 Docker 的--network host模式。
默认情况下容器用 bridge 网络,有自己的 IP 和虚拟网卡,和宿主机是“两栋楼,中间通过网线连接”。而--network host模式下,容器直接共享宿主机网络栈,相当于容器里的进程“住进了宿主机这栋楼的房间”,端口监听直接在宿主机上,不经过任何 NAT 或端口映射。
docker run --network host -d --name myapp myimage这个模式的优缺点要心里有数:
- 好处:网络性能几乎没有损耗,宿主机上能访问的 IP 和端口,容器里也能直接访问,配置 Kafka、Redis 等需要与宿主机大量交互的服务很方便;
- 坏处:容器隔离性下降,端口冲突风险大。
“什么时候选 bridge、什么时候选 host”没有绝对标准。我自己的经验是:生产环境的微服务尽量走 bridge + 自定义网络,用服务名互相调用,可移植性强;只有本地调试或对网络延迟极度敏感的场景,才考虑 host。
5.5 权限问题:容器内文件所有者和宿主机用户对不上
容器里创建的文件,经常在宿主机上是 root 所有,或者宿主机用户账户在容器里访问不了。特别常见于挂载卷:容器以 root 跑,写出来的文件归 root;宿主机上你用自己的普通账号想去改,发现没有权限。
解决思路一般有:
- 启动容器时指定用户:
docker run --user 1000:1000,让容器内进程以宿主机账户的 UID/GID 运行; - 镜像构建时提前创建对应 UID 的用户,而不是盲目用 root;
- 在容器内外共用挂载目录时,尽量统一 UID/GID。
这个坑不解决,后面做 CI、做文件交换时会在“文件权限拒绝”上浪费很多时间。
6. 镜像安全和容器安全:名字听起来像,加固思路完全不一样
热搜里出现了“镜像安全 和容器安全”,我把这两个也一并讲清楚。它们的关系恰好又回到镜像和容器的区别上:一个是静态的,一个是动态的。
6.1 镜像安全:从源头到静态文件,逐层审查
镜像安全关注的是**“这张图纸本身有没有问题”**。镜像一旦有漏洞、包含恶意软件、或者带着敏感信息,那么基于它创建的所有容器都会遭殃。所以镜像安全的重心全在“源头和静态内容”:
- 尽量从官方仓库或可信源拉取镜像,对拉下来的镜像做 hash 校验;
- 不要轻易使用来路不明的精简镜像,某些第三方镜像会删掉安全组件,甚至埋后门;
- 定期做漏洞扫描,常见的工具有 Trivy、Clair 等;
- 镜像内不要写死口令、密钥、令牌,改用环境变量或密钥管理服务注入;
- 遵循最小化原则:基于瘦身基础镜像、不安装多余工具、减少攻击面。
一句话总结:镜像安全就是“你不会让陌生人拿着可疑图纸给你盖楼”。
6.2 容器安全:关注动态运行期的隔离和权限
容器安全关注的是**“楼盖好之后,住客在里面的行为”**,核心是资源隔离和权限控制。
Docker 容器之所以不像虚拟机一样拥有完整内核隔离,是因为它靠的是 Linux 内核的 Namespace 和 Cgroups 两类技术:
- Namespace 让容器看到各自的进程、网络、文件系统、用户空间;
- Cgroups 限制每个容器能使用的 CPU、内存、IO 等资源。
这就引出两个关键实践:
- 给容器加资源限制。不给限制的容器可以吃光宿主机全部 CPU 和内存,影响其他容器甚至宿主机本身。启动时建议明确指定:
docker run -d --name myapp --cpus="1.5" --memory="512m" myimage别小看这一步,生产环境里没有资源限制的容器,就是一颗不定时炸弹。
- 尽量不用
--privileged特权模式。特权模式等于把容器的权限边界直接拆了,容器内进程可以访问宿主机的绝大多数设备。除非有明确且必要的原因,否则我强烈不建议用。
“容器资源隔离”这个词,网上聊起来好像很玄,实际落到操作层面就是 Namespace 加 Cgroups 的配合。理解这个底层,再去看docker stats输出的 CPU/内存指标,你会更清楚它背后的含义。
7. 如果你刚开始学 Docker,我的学习路线建议
最后这部分不是总结,是我带过不少人之后沉淀下来的学习顺序。你要是完全零基础,按这个顺序走,会比直接四处抄命令快得多,也不容易陷入“镜像容器傻傻分不清”的泥潭。
第一步,先把“镜像只读、容器可写、一个镜像多个容器”这组关系背熟,并用最原始的命令把基本操作走一遍:docker pull、docker run、docker ps、docker exec、docker stop、docker rm。不要上来就写 Docker Compose,Compose 是让你同时管理多个容器的工具,前提是你得先理解单个容器。
第二步,动手写第一份 Dockerfile。不用追求花哨,就做一个能跑 Hello World 的小服务,把FROM、RUN、COPY、CMD四个指令吃透。感受到“每一条指令生成一个层”这个过程,镜像分层的概念就会真正刻进脑子里。
第三步,练习数据持久化。故意用一个没挂卷的容器写点文件,然后删掉它,亲眼看看数据是怎么丢的;再挂上-v重来一,对比差异。这个教训自己踩一次,比看十篇教程都深刻。
第四步,试着用同一个镜像部署两个容器,感受“共享镜像但互不干扰”是怎样的体验。这一步可以布置一个小作业:用 nginx 镜像起两个容器,分别映射到 8080 和 8081 端口,各自挂载不同首页。
第五步,再进入 Docker Compose、网络模式、资源限制这些进阶话题。你会发现,只要前面几步打下了镜像与容器关系的基础,后面那些工具都只是“让这张图纸在更多楼之间高效协作”的锦上添花。
最后说句个人经验:我这些年排查过的 Docker 问题,不管表象多复杂,往深了挖,八成都要回归到“镜像和容器到底谁是谁”这个最基础的关系上。把这一对关系刻在脑子里,比记一百条命令都管用。