镜像和容器的本质区别在于:镜像是由多个只读层堆叠而成的静态文件模板,而容器是在镜像基础上叠加可写层并附加命名空间、cgroups等运行时隔离机制的动态实例;镜像不可修改、可被多容器共享,容器的可写层随生命周期消亡,数据持久化需依赖卷或绑定挂载。
镜像和容器不是“模板 vs 实例”这么简单的一句话能概括的。真正区分它们,得看底层文件系统怎么组织、运行时怎么隔离、数据怎么存、删了之后东西去哪儿——这些才是实际工作中天天打交道的细节。
镜像是只读分层堆叠的静态文件集合
镜像不是单个大文件,而是一串按顺序叠加的只读层(Layer),比如基础系统层、依赖安装层、配置层、代码层。Docker 用联合文件系统(如 overlay2)把它们“叠”成一个统一视图。每一层都有唯一内容哈希(content-addressable),相同层可被多个镜像共享——比如十个 Python 应用镜像共用同一个 python:3.11-slim 基础层,磁盘只存一份。
- 构建靠 docker build 或 docker commit(后者不推荐)
- 拉取后存在本地 /var/lib/docker/overlay2/ 下,路径由哈希决定
- 你改不了它:docker pull 下来的镜像,docker run 启动前不会动它半点
容器是镜像+可写层+运行时元数据的组合体
容器启动时,Docker 在镜像最上层动态挂载一个**可写层(container layer)**,所有运行时改动——比如日志写入、临时文件、apt install、甚至改了 /etc/hosts——都只发生在这层。镜像层完全不动,其他容器照样能复用原镜像。
- 这个可写层生命周期绑定容器:容器删了,这层自动销毁(除非用了 --rm=false 且手动保存)
- 除文件系统外,容器还带完整命名空间(pid、net、mnt、user 等)、cgroups 资源限制、网络栈、进程树 —— 它本质是一个被深度隔离的进程组
- docker ps 看到的是正在运行的容器;docker ps -a 还能看到已退出但元数据仍在的容器(它的可写层可能还占着磁盘)
数据持久化不能靠改容器,得靠卷或绑定挂载
很多人误以为“我在容器里改了 config.yml,重启还能用”,结果发现配置丢了——因为重启容器会新建可写层,旧层没了。这就是没分清“容器层”和“持久数据”的区别。
- 想保留数据,必须用 docker volume create 创建命名卷,或用 -v /host/path:/container/path 绑定宿主机目录
- 卷独立于容器生命周期:容器删了,卷还在;新容器挂同一卷,就能拿到老数据
- 镜像里写的 COPY 或 ADD 是构建时固化进只读层的,运行时不可覆盖(除非用 docker commit 生成新镜像,但这是反模式)
删了容器,镜像不一定安全;删了镜像,容器一定起不来
执行 docker rm xxx 只清理容器元数据 + 可写层,镜像毫发无损。但如果你顺手 docker rmi xxx 把镜像删了,再想 docker run 就会报错 “No such image”。更隐蔽的问题是:多个容器共用一个镜像,删镜像前得确认没人在用。
- 悬空镜像(dangling image)是构建中断或 docker build --no-cache 后残留的中间层,不占应用逻辑,但吃磁盘——用 docker image prune 清理
- 容器退出后若没删,它的可写层仍占空间,docker system df 能看到“Reclaimable”列提示可回收量
- 真正释放磁盘,要 docker container prune + docker image prune -a 配合使用


















