Docker 镜像和容器傻傻分不清楚?这篇讲清楚了
记得刚开始学 Docker 的时候,文档里"镜像"和"容器"轮流出现,看第一遍以为懂了,实际操作的时候又糊了——docker run、docker start、docker create,到底操作的是镜像还是容器?
这两个概念是 Docker 的地基,搞不清楚后面会一直绊脚。这篇专门把它讲透。
用一个类比先建立直觉
先不谈技术,说个生活里的例子。
镜像就像一张光盘(或者 ISO 文件)。里面刻好了操作系统、软件、配置,烧好就不能改了。你可以把同一张光盘插到 10 台电脑上,每台电脑跑起来的系统是独立的,互不干扰。
容器就是这台"跑起来的电脑"。它从光盘启动,可以往里面写文件、改配置、装软件——但这些改动只存在于这台运行中的机器里,不会写回光盘。你把这台电脑关了,改动就没了(除非你做了额外的持久化操作)。
这个比喻不完美,但能帮你建立第一印象:
一个镜像可以同时派生出多个容器,每个容器相互独立,互不影响。
镜像是什么
镜像(Image)是一个只读的文件系统快照,里面打包了:
- 基础操作系统环境(比如 Ubuntu、Alpine)
- 应用程序代码
- 依赖库(Python 包、npm 模块、Java jar 等)
- 环境变量、默认配置
- 启动命令
只读这两个字很重要。你拉下来一个 nginx 镜像,你没办法直接改它里面的文件——所有改动只能发生在基于它创建的容器里。
镜像的分层结构
镜像不是一整块数据,而是一层一层叠起来的。
每一层对应 Dockerfile 里的一条指令,只记录相对于上一层的差异。这个设计有两个好处:
好处一:复用。多个镜像共享同一个底层(比如都基于 Ubuntu),底层只存一份,不重复占用磁盘。
好处二:缓存。重新构建镜像时,没变过的层直接用缓存,只重新构建改了的层及其往上的部分。依赖层没变、只改了代码,重建速度快很多。
用 docker history 可以看到一个镜像的分层情况:
docker history nginx
# IMAGE CREATED CREATED BY SIZE
# a72860cb95fd 2 weeks ago CMD ["nginx" "-g" "daemon off;"] 0B
# <missing> 2 weeks ago STINGER args EXPOSE 80 0B
# <missing> 2 weeks ago COPY /etc/nginx /etc/nginx 28.6MB
# <missing> 2 weeks ago RUN apt-get install -y nginx ...
# <missing> 2 weeks ago FROM debian:bookworm-slim 74.8MB
从下往上读,最底层是基础系统,往上每层叠一个操作。
容器是什么
容器(Container)是基于镜像运行起来的实例。
启动容器时,Docker 在镜像的只读层上面加了一层可写层(叫做 Container Layer 或 Thin Writable Layer)。你在容器里做的所有改动——新建文件、修改配置、安装软件——都写在这个可写层,不会碰底下的只读层。
容器停掉后,可写层的内容默认就消失了。 这不是 bug,是设计——容器天生是短暂的、无状态的。如果你需要数据持久化,要用 Volume(后面专门讲)。
容器的生命周期
一个容器从创建到消失,要经历几个状态:
几个关键点:
docker run=docker create+docker start的合并版,直接从镜像创建并启动docker stop是优雅停止,给进程发 SIGTERM 信号,等它自己退出(默认等 10 秒)docker kill是强制终止,直接 SIGKILL,立刻死- 停掉(Stopped)的容器还在磁盘上,占空间,用
docker rm才算真正删掉
最容易混淆的几个操作
把镜像和容器的操作命令放在一起对比,清晰多了:
常见困惑1:docker ps 看不到我的容器
docker ps 默认只显示运行中的容器。加 -a 参数才显示所有状态,包括已停止的:
docker ps # 只看运行中的
docker ps -a # 看全部,包括 Exited 状态的
常见困惑2:docker rm 报错说容器还在运行
不能直接删除运行中的容器,要先停再删:
docker stop my-container
docker rm my-container
# 或者一步到位(强制)
docker rm -f my-container
常见困惑3:删了容器,镜像也跟着没了吗
不会。容器和镜像是独立的。删容器不影响镜像,镜像还在本地。要删镜像要单独执行 docker rmi:
docker rmi nginx # 删 nginx 镜像
docker rmi nginx:1.24 # 删特定版本
但注意:有容器还在用这个镜像时,直接删镜像会报错(即使容器是停止状态)。需要先把依赖这个镜像的容器全删掉,才能删镜像。
一次完整操作演示
从零拉一个镜像,创建容器,进去逛一圈,然后清理掉:
# 1. 拉取镜像
docker pull ubuntu:22.04
# 输出:
# 22.04: Pulling from library/ubuntu
# 857cc8cb19c0: Pull complete
# Digest: sha256:...
# Status: Downloaded newer image for ubuntu:22.04
# 2. 确认镜像在本地
docker images ubuntu
# REPOSITORY TAG IMAGE ID CREATED SIZE
# ubuntu 22.04 a72860cb95fd 2 weeks ago 77.9MB
# 3. 从镜像创建并启动容器(-it 交互模式,--rm 退出后自动删除)
docker run -it --rm --name test-ubuntu ubuntu:22.04 bash
# 现在你在容器内部了,提示符变了:
root@7f3a2b1c9e05:/#
# 4. 在容器里随便搞,比如建个文件
echo "hello from container" > /tmp/test.txt
cat /tmp/test.txt
# hello from container
# 5. 退出容器
exit
# 6. 因为用了 --rm,容器自动删了
docker ps -a # 看不到 test-ubuntu 了
# 7. 但镜像还在
docker images ubuntu
# ubuntu 22.04 a72860cb95fd 2 weeks ago 77.9MB
这个流程跑一遍,镜像/容器的关系就有感觉了。
把改动保存回镜像?docker commit
容器里改了东西,想保留下来怎么办?可以用 docker commit 把当前容器的状态打成一个新镜像:
# 先启动一个容器,装点东西
docker run -it --name my-ubuntu ubuntu:22.04 bash
apt-get update && apt-get install -y vim curl
exit
# 把这个容器的状态保存成新镜像
docker commit my-ubuntu my-ubuntu-with-tools:v1
# 验证
docker images my-ubuntu-with-tools
# REPOSITORY TAG IMAGE ID SIZE
# my-ubuntu-with-tools v1 b3c2a1d9f7e8 165MB
这个方法能用,但不推荐在生产中这样做。原因是这种方式构建的镜像没有构建历史,别人接手时根本不知道你装了什么、怎么装的。推荐的做法是写 Dockerfile,把所有步骤显式记录下来——下一篇就讲这个。
小结
概念搞清楚之后,操作就顺了:
| 对比项 | 镜像 | 容器 |
|---|---|---|
| 本质 | 只读文件系统快照 | 运行中的镜像实例 |
| 生命周期 | 持久,手动删才消失 | 短暂,停止后可清理 |
| 可写性 | 不可写 | 有可写层 |
| 关系 | 一对多(一个镜像 → 多个容器) | 多对一(多个容器依赖同一镜像) |
| 删除命令 | docker rmi |
docker rm |
理解这两个概念之后,Dockerfile 看起来就清晰多了——它就是"描述如何一层层构建镜像"的配方文件。
欢迎关注我
更多推荐





所有评论(0)