目录

Docker 镜像教程:pull、images、tag、rmi 与分层结构

镜像(image)是创建容器的模板。上一节的 docker run nginx:1.30 背后,Docker 先下载了 nginx:1.30 这个镜像。本节说明镜像如何命名、下载、查看和删除,以及它“一层一层”的结构。

镜像名称的组成

一个完整的镜像名称如下:

docker.io/library/nginx:1.30-alpine
└───┬───┘ └──┬──┘ └─┬─┘ └────┬────┘
 registry  命名空间  名称     tag
部分说明省略时的默认值
registry存放镜像的服务器docker.io(Docker Hub)
命名空间用户或组织名称官方镜像是 library
名称(repository)镜像名称不可省略
tag版本标签latest

所以下面三种写法指的是同一个镜像:

docker pull nginx
docker pull nginx:latest
docker pull docker.io/library/nginx:latest

其他 registry 的镜像需要写出完整地址,例如 GitHub 的 ghcr.io/owner/app:1.0、Google 的 gcr.io/distroless/static-debian13。

常见的 tag 命名

官方镜像通常同时提供多种变体:

tag 示例含义
python:3.14基于 Debian 的完整版,包含编译工具等大量软件包
python:3.14-slim精简版 Debian,只保留运行 Python 所需的内容
python:3.14-alpine基于 Alpine Linux,体积最小
node:24-trixie指定 Debian 版本代号(trixie = Debian 13)
nginx:1.30 / nginx:1.31nginx 的 stable(偶数)与 mainline(奇数)版本

本节后面会实际下载这三种 Python 镜像,并比较它们的大小。

下载镜像:docker pull

如果本机没有镜像,运行 docker run 时会自动下载;docker pull 则只下载,不运行。对已经有的镜像再次执行 pull,Docker 会检查是否有更新:

$ docker pull nginx:1.30
1.30: Pulling from library/nginx
Digest: sha256:9bf97bd7714f5e24c1ccd545ecb9eb5435cb6d109c97cebb15e7e455e0239edb
Status: Image is up to date for nginx:1.30
docker.io/library/nginx:1.30

Image is up to date 表示本机上的已经是最新版本,无需重新下载。最后一行是镜像的完整名称,可以看到默认的 registry docker.io 与命名空间 library。

亲眼看看“共享层”

接着下载 nginx 的另一个版本 1.31:

$ docker pull nginx:1.31
1.31: Pulling from library/nginx
ecc510c1e359: Already exists
807ab930f3a0: Pull complete
04bdbc7ea6b5: Pull complete
f7e1db57b58b: Pull complete
8020170dc65f: Pull complete
1efc53192927: Pull complete
947d22046304: Pull complete
Digest: sha256:f9ea18bfa4fad859e1ed38259d711da7ccad2c3516e875cec3351a57c859f571
Status: Downloaded newer image for nginx:1.31
docker.io/library/nginx:1.31

第一层 ecc510c1e359 显示 Already exists:nginx 1.30 和 1.31 都以同一个 Debian 13 基础层开始,这一层已经在硬盘上,无需重新下载。这就是第 1 节所说的“镜像共享底层”。

下载 python:3.14-slim 时也一样,它同样基于 Debian 13:

$ docker pull python:3.14-slim
3.14-slim: Pulling from library/python
ecc510c1e359: Already exists
a5883d972f3b: Pull complete
6aaf5b0aae75: Pull complete
bb8166eb34e6: Pull complete
Digest: sha256:a2b82f3c48559aa0a8446d9af49826b6e2b2016f4cd2afabfe6013ec53729170
Status: Downloaded newer image for python:3.14-slim
docker.io/library/python:3.14-slim

查看本机镜像:docker images

$ docker images
IMAGE                ID             DISK USAGE   CONTENT SIZE   EXTRA
hello-world:latest   5e2309035332       25.9kB         9.49kB   U
nginx:1.30           9bf97bd7714f        242MB         66.5MB
nginx:1.31           f9ea18bfa4fa        243MB         66.7MB
ubuntu:26.04         f144425ff09b        162MB         45.6MB

Docker 29 的 docker images 输出格式与旧版不同。旧版字段是 REPOSITORY TAG IMAGE ID CREATED SIZE,网上很多教程仍然使用旧格式;看到不一样不用紧张,内容是一样的。

字段含义
IMAGE名称与 tag
ID镜像 ID 的前 12 位
DISK USAGE解压后在硬盘上占用的空间
CONTENT SIZE压缩后的内容大小,大约等于下载量
EXTRAU 表示有容器正在使用(In use),这里是上一节留下的 hello-world 容器

docker image ls 和 docker images 是同一个命令。常用的筛选方式:

$ docker images -q
b9767f56a1cc
a2b82f3c4855
9bf97bd7714f
f9ea18bfa4fa
f6a589d43c42
f144425ff09b
5e2309035332

-q 只输出 ID,常用于搭配其他命令进行批量处理。docker images nginx 只列出 nginx,docker images --filter dangling=true 列出没有名称的“悬空”镜像(通常是重新构建后被替换的旧版本)。

比较 slim、alpine 与完整版

再下载另外两个 Python 版本:

$ docker pull python:3.14-alpine
3.14-alpine: Pulling from library/python
e2de96513ba9: Pull complete
48f6a2588427: Pull complete
2a22c5887fa2: Pull complete
d486488257d0: Pull complete
Digest: sha256:f6a589d43c42b9e7f7dc67a12d37132491f362859a5d750607710cc56da3bc72
Status: Downloaded newer image for python:3.14-alpine
docker.io/library/python:3.14-alpine

$ docker pull python:3.14
3.14: Pulling from library/python
4846f2900730: Pull complete
62fe2e747f69: Pull complete
4dc3950125eb: Pull complete
b2eb680928d4: Pull complete
083b5813628d: Pull complete
9b0ef59a9f4a: Pull complete
6e255bfcc16c: Pull complete
Digest: sha256:b9767f56a1cc620aa72d38cc3557d26412e38a4597c92953476136b78894ccbd
Status: Downloaded newer image for python:3.14
docker.io/library/python:3.14
$ docker images python
IMAGE                ID             DISK USAGE   CONTENT SIZE   EXTRA
python:3.14          b9767f56a1cc       1.63GB          433MB
python:3.14-alpine   f6a589d43c42       82.6MB           21MB
python:3.14-slim     a2b82f3c4855        180MB         45.7MB

同样是 Python 3.14,差距非常大:

版本下载量(CONTENT SIZE)硬盘空间(DISK USAGE)
python:3.14433 MB1.63 GB
python:3.14-slim45.7 MB180 MB
python:3.14-alpine21 MB82.6 MB
  • 完整版包含 gcc 等编译工具,适合需要在镜像中编译软件包的情况(或作为多阶段构建的构建阶段)。
  • slim 是大部分应用程序的好选择。
  • alpine 体积最小,但它使用 musl 而不是 glibc,有些需要编译的软件包(例如部分 Python 包)在 Alpine 上安装会比较麻烦。不确定时,选 slim 通常更稳妥。

给镜像添加标签:docker tag

docker tag 不会复制镜像,只是添加另一个名称,使其指向同一份内容:

$ docker tag nginx:1.30 myname/nginx:v1

$ docker images --filter reference=nginx:1.30 --filter reference=myname/nginx
IMAGE             ID             DISK USAGE   CONTENT SIZE   EXTRA
myname/nginx:v1   9bf97bd7714f        242MB         66.5MB
nginx:1.30        9bf97bd7714f        242MB         66.5MB

两行的 ID 相同,表示它们是同一个镜像。推送到 Docker Hub 前,需要用这种方式将镜像命名为 你的账号/名称:tag,见推送到 Docker Hub。

tag 不等于固定版本

tag 只是一个“标签”,维护者可以随时将它指向新的内容,nginx:1.30 下个月可能会指向 1.30.6。需要完全固定时,可以使用 digest(内容的 SHA256 哈希):

$ docker images --digests nginx
REPOSITORY   TAG       DIGEST                                                                    IMAGE ID       CREATED      SIZE
nginx        1.30      sha256:9bf97bd7714f5e24c1ccd545ecb9eb5435cb6d109c97cebb15e7e455e0239edb   9bf97bd7714f   6 days ago   242MB
nginx        1.31      sha256:f9ea18bfa4fad859e1ed38259d711da7ccad2c3516e875cec3351a57c859f571   f9ea18bfa4fa   6 days ago   243MB
docker pull nginx@sha256:9bf97bd7714f5e24c1ccd545ecb9eb5435cb6d109c97cebb15e7e455e0239edb

使用 digest 下载,永远会得到完全相同的内容。

删除镜像:docker rmi

如果有容器(包括已停止的容器)正在使用这个镜像,就无法删除:

$ docker run -d --name web nginx:1.30
e34409bcf1727f02fc3b0558d202dd057f2116b4a2e9186f83bbed4625842272

$ docker rmi nginx:1.30
Error response from daemon: conflict: unable to delete nginx:1.30 (must be forced) - container e34409bcf172 is using its referenced image 9bf97bd7714f

先删除使用它的容器,再删除镜像:

$ docker rm -f web
web

$ docker rmi nginx:1.30
Untagged: nginx:1.30
Deleted: sha256:9bf97bd7714f5e24c1ccd545ecb9eb5435cb6d109c97cebb15e7e455e0239edb
  • Untagged:移除名称。
  • Deleted:没有其他名称指向它时,镜像本身才会真正删除。如果还有 myname/nginx:v1 这类别名指向同一个镜像,就只会看到 Untagged。

清理不需要的镜像:

docker image prune      # 只删除悬空镜像(没有名称的 <none>)
docker image prune -a   # 删除所有未被容器使用的镜像

镜像的分层结构:docker history

docker history 会列出镜像的每一层,以及这一层由 Dockerfile 的哪个命令生成:

$ docker history nginx:1.30
IMAGE          CREATED      CREATED BY                                      SIZE      COMMENT
9bf97bd7714f   6 days ago   CMD ["nginx" "-g" "daemon off;"]                0B        buildkit.dockerfile.v0
<missing>      6 days ago   STOPSIGNAL SIGQUIT                              0B        buildkit.dockerfile.v0
<missing>      6 days ago   EXPOSE map[80/tcp:{}]                           0B        buildkit.dockerfile.v0
<missing>      6 days ago   ENTRYPOINT ["/docker-entrypoint.sh"]            0B        buildkit.dockerfile.v0
<missing>      6 days ago   COPY 30-tune-worker-processes.sh /docker-ent…   16.4kB    buildkit.dockerfile.v0
<missing>      6 days ago   COPY 20-envsubst-on-templates.sh /docker-ent…   12.3kB    buildkit.dockerfile.v0
<missing>      6 days ago   COPY 15-local-resolvers.envsh /docker-entryp…   12.3kB    buildkit.dockerfile.v0
<missing>      6 days ago   COPY 10-listen-on-ipv6-by-default.sh /docker…   12.3kB    buildkit.dockerfile.v0
<missing>      6 days ago   COPY docker-entrypoint.sh / # buildkit          8.19kB    buildkit.dockerfile.v0
<missing>      6 days ago   RUN /bin/sh -c set -x     && groupadd --syst…   88.2MB    buildkit.dockerfile.v0
<missing>      6 days ago   ENV DYNPKG_RELEASE=1~trixie                     0B        buildkit.dockerfile.v0
<missing>      6 days ago   ENV PKG_RELEASE=1~trixie                        0B        buildkit.dockerfile.v0
<missing>      6 days ago   ENV ACME_VERSION=0.4.1                          0B        buildkit.dockerfile.v0
<missing>      6 days ago   ENV NJS_RELEASE=1~trixie                        0B        buildkit.dockerfile.v0
<missing>      6 days ago   ENV NJS_VERSION=1.0.1                           0B        buildkit.dockerfile.v0
<missing>      6 days ago   ENV NGINX_VERSION=1.30.5                        0B        buildkit.dockerfile.v0
<missing>      6 days ago   LABEL maintainer=NGINX Docker Maintainers <d…   0B        buildkit.dockerfile.v0
<missing>      7 days ago   # debian.sh --arch 'amd64' out/ 'trixie' '@1…   87.7MB    debuerreotype 0.17

从下往上读:

  1. 最底层是 Debian 13(trixie)基础层,87.7 MB。
  2. 中间的 RUN ... groupadd ... 安装了 nginx 本身,88.2 MB,是最大的一层。
  3. COPY 放入启动脚本,只有几 KB。
  4. ENV、EXPOSE、CMD 等只是设置,大小为 0B。

<missing> 不是错误,只表示这些中间层是在其他机器上构建的,本机没有它们各自独立的 ID。

分层有两个好处:

  • 共享:多个镜像都以 Debian 13 为基础时,该层只存一份、只下载一次(前面的 Already exists)。
  • 缓存:构建镜像时,没有变化的层可以直接复用,这是构建缓存的基础。

容器启动时,Docker 会在这些只读层上面添加一层可写层。容器内新增或修改的文件都写在这一层,容器删除时这一层也会随之消失,因此需要保存的数据应放在 Volume 中。

在 Docker Desktop 中点击某个镜像,也可以看到相同的分层信息:

Docker Desktop 显示镜像分层
Docker Desktop 镜像详情页:Layers 列出每一层的命令和大小。图片来源:Docker 官方文档(Apache-2.0)

查看镜像设置:docker inspect

docker inspect 会输出镜像的完整设置(一大段 JSON)。使用 --format 只提取需要的字段:

$ docker inspect --format '{{.Config.Cmd}}' nginx:1.30
[nginx -g daemon off;]

$ docker inspect --format '{{.Os}}/{{.Architecture}}' nginx:1.30
linux/amd64

$ docker inspect --format '{{json .Config.ExposedPorts}}' nginx:1.30
{"80/tcp":{}}

$ docker inspect --format '{{range .Config.Env}}{{println .}}{{end}}' nginx:1.30
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
NGINX_VERSION=1.30.5
NJS_VERSION=1.0.1
NJS_RELEASE=1~trixie
ACME_VERSION=0.4.1
PKG_RELEASE=1~trixie
DYNPKG_RELEASE=1~trixie

从中可以知道:容器启动时默认运行 nginx -g daemon off;、这是 linux/amd64 版本、开放了 80 端口,以及镜像内置的环境变量。

导出与导入:docker save / load

在无法联网的机器之间迁移镜像时,可以将镜像保存为一个 tar 文件:

$ docker save -o nginx-1.30.tar nginx:1.30

$ ls -lh nginx-1.30.tar
-rw-------    1 root     root       63.4M Oct 12 14:41 nginx-1.30.tar

$ docker rmi nginx:1.30
Untagged: nginx:1.30
Deleted: sha256:9bf97bd7714f5e24c1ccd545ecb9eb5435cb6d109c97cebb15e7e455e0239edb

$ docker load -i nginx-1.30.tar
Loaded image: nginx:1.30

将 tar 文件复制到另一台机器,运行 docker load 就能使用,无需连接 Docker Hub。

用 Docker Desktop 管理镜像

在 Docker Desktop 左侧菜单中点击 Images,可以查看本机所有镜像,相当于运行 docker images;右侧的按钮可以直接运行或删除镜像:

Docker Desktop 的 Images 界面
Images 界面列出本机镜像、tag、大小与创建时间。图片来源:Docker 官方文档(Apache-2.0)

命令汇总

命令作用
docker pull 名称:tag下载镜像
docker images列出本机镜像
docker images -q只列出 ID
docker tag 来源 新名称添加新的名称 / 标签
docker rmi 名称删除镜像
docker image prune -a删除所有未使用的镜像
docker history 名称查看分层
docker inspect 名称查看设置
docker save -o a.tar 名称导出为 tar 文件
docker load -i a.tar从 tar 文件导入

下一节介绍容器从创建到删除会经历哪些状态。

重点回顾

  • 镜像名称是 [registry/]储存库:tag,官方镜像没有前缀。
  • 多个镜像可以共享相同的底层,只下载、存储一次。
  • slim 是多数应用程序的好选择;alpine 体积最小,但兼容性较差。
  • docker history 显示每一层由哪个命令生成。

动手练习

练习 1:下载 alpine:3.23 和 python:3.14-alpine,观察第二次下载时是否出现 Already exists。

提示

如果两者使用相同的 Alpine 基础层,第二次下载会显示该层 Already exists,无需重新下载。使用 docker history 可以比较两者最底层的内容。

练习 2:给 nginx:1.30 添加一个新名称 mysite/web:test,再删除这个新名称。原来的 nginx:1.30 还在吗?

答案
docker tag nginx:1.30 mysite/web:test
docker rmi mysite/web:test

还在。docker rmi 删除名称时只会显示 Untagged;只要镜像还有其他名称,实际内容就不会被删除。

常见问题

latest 是最新版吗?

不一定。latest 只是一个普通的 tag 名称,由作者决定指向哪个版本。生产环境请写明确的版本,例如 nginx:1.30。

Docker 29 的 docker images 字段怎么和网上的不一样?

Docker 29 改成 IMAGE ID DISK USAGE CONTENT SIZE EXTRA,旧版是 REPOSITORY TAG IMAGE ID CREATED SIZE,见本章说明。

该选 slim 还是 alpine?

大部分应用程序选 slim。alpine 最小,但使用 musl libc,部分需要编译的包(特别是 Python)安装时可能失败或很慢。

更新于 2026年10月14日