记得刚开始学 Docker 的时候,文档里"镜像"和"容器"轮流出现,看第一遍以为懂了,实际操作的时候又糊了——docker rundocker startdocker create,到底操作的是镜像还是容器?

这两个概念是 Docker 的地基,搞不清楚后面会一直绊脚。这篇专门把它讲透。


用一个类比先建立直觉

先不谈技术,说个生活里的例子。

镜像就像一张光盘(或者 ISO 文件)。里面刻好了操作系统、软件、配置,烧好就不能改了。你可以把同一张光盘插到 10 台电脑上,每台电脑跑起来的系统是独立的,互不干扰。

容器就是这台"跑起来的电脑"。它从光盘启动,可以往里面写文件、改配置、装软件——但这些改动只存在于这台运行中的机器里,不会写回光盘。你把这台电脑关了,改动就没了(除非你做了额外的持久化操作)。

这个比喻不完美,但能帮你建立第一印象:

docker run

docker run

docker run

🖼️ 镜像
(只读模板)

📦 容器 1
(运行中的实例)

📦 容器 2
(运行中的实例)

📦 容器 3
(运行中的实例)

一个镜像可以同时派生出多个容器,每个容器相互独立,互不影响。


镜像是什么

镜像(Image)是一个只读的文件系统快照,里面打包了:

  • 基础操作系统环境(比如 Ubuntu、Alpine)
  • 应用程序代码
  • 依赖库(Python 包、npm 模块、Java jar 等)
  • 环境变量、默认配置
  • 启动命令

只读这两个字很重要。你拉下来一个 nginx 镜像,你没办法直接改它里面的文件——所有改动只能发生在基于它创建的容器里。

镜像的分层结构

镜像不是一整块数据,而是一层一层叠起来的

Layer 0:Ubuntu 22.04 基础层
(来自 ubuntu:22.04 镜像)

Layer 1:安装 Python 3.11
apt install python3

Layer 2:安装依赖
pip install -r requirements.txt

Layer 3:复制应用代码
COPY . /app

Layer 4:设置启动命令
CMD [python, app.py]

每一层对应 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)。你在容器里做的所有改动——新建文件、修改配置、安装软件——都写在这个可写层,不会碰底下的只读层。

只读层:Ubuntu 基础

只读层:安装 Python

只读层:复制应用代码

可写层(容器层)
运行时的所有改动都在这里

容器停掉后,可写层的内容默认就消失了。 这不是 bug,是设计——容器天生是短暂的、无状态的。如果你需要数据持久化,要用 Volume(后面专门讲)。

容器的生命周期

一个容器从创建到消失,要经历几个状态:

docker create

docker start

docker pause

docker unpause

docker stop / 进程退出

docker start

docker rm

docker rm -f(强制删除)

Created

Running

Paused

Stopped

几个关键点:

  • docker run = docker create + docker start 的合并版,直接从镜像创建并启动
  • docker stop 是优雅停止,给进程发 SIGTERM 信号,等它自己退出(默认等 10 秒)
  • docker kill 是强制终止,直接 SIGKILL,立刻死
  • 停掉(Stopped)的容器还在磁盘上,占空间,用 docker rm 才算真正删掉

最容易混淆的几个操作

把镜像和容器的操作命令放在一起对比,清晰多了:

容器操作

镜像操作

产出

产出

用于

docker pull
从仓库拉取镜像

docker build
用 Dockerfile 构建镜像

docker images
列出本地所有镜像

docker rmi
删除镜像

docker push
推送镜像到仓库

docker run
从镜像创建并启动容器

docker ps
列出运行中的容器

docker stop
停止容器

docker rm
删除容器

docker exec
进入运行中的容器执行命令

常见困惑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 看起来就清晰多了——它就是"描述如何一层层构建镜像"的配方文件。

欢迎关注我

Docker Compose 入门:一个命令跑起整套环境

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐