第 2 章 镜像管理
本章目标:掌握镜像的拉取、查看、标记、删除、导出导入,理解镜像分层与"写时复制"原理,并了解镜像仓库的运作方式。
1. 镜像是怎么来的
镜像是分层打包的只读文件系统 + 元数据(配置、默认环境变量、入口命令等)。来源有三个:
- 公共仓库拉取:最常用的是 Docker Hub(
docker.io),包含官方镜像与社区镜像; - 私有仓库拉取:公司内部自建的 registry(如 Harbor、Docker Registry);
- 自己构建:用 Dockerfile 构建(第 6 章)。
镜像的完整名字格式:
text
[registry地址/][命名空间/]镜像名[:标签]
docker.io/library/nginx:1.27
└─────────┬────────┘ └──┬──┘ └─┬─┘
registry 命名空间 标签(默认 latest)docker.io/library/可以省略,docker pull nginx等价于docker pull docker.io/library/nginx:latest;- 命名空间
library是官方镜像专用;社区镜像形如bitnami/postgresql; - 标签(tag)默认是
latest,但它会随版本变动——生产环境应固定具体版本号。
2. 镜像的基本管理
| 命令 | 作用 |
|---|---|
docker pull <image> | 拉取镜像到本地 |
docker images(或 docker image ls) | 列出本地镜像 |
docker tag <镜像> <新名字:标签> | 打标签(复制引用,不复制数据) |
docker rmi <image> | 删除镜像 |
docker image prune | 清理悬空镜像(dangling) |
docker history <image> | 查看镜像的分层历史 |
一组完整的操作序列:
bash
docker pull nginx:alpine # 拉取镜像
docker images # 查看本地镜像
docker tag nginx:alpine mynginx:v1 # 打一个自己的标签
docker rmi nginx:alpine # 删除原标签
docker images | Select-String mynginx # 标签仍在(同一个镜像)要点:
tag只是给同一个镜像增加一个引用(类似文件硬链接),不复制镜像数据;rmi删除的是标签/引用;只有镜像的所有标签都被删除、且没有容器引用时,镜像数据才会真正释放;- 删除被容器使用的镜像会报错:
image is being used by running container,需要先删除(或停止)容器。
3. 镜像分层:为什么又省又快
镜像由多层只读层叠加而成,每层都是文件系统的一次变更集合:
text
┌─────────────────────────┐ ← 可写层(只有容器有,容器删除即消失)
├─ 层 3:COPY 应用代码 │ ← 只读,build 时生成
├─ 层 2:安装依赖库 │ ← 只读
├─ 层 1:基础镜像层 │ ← 只读,如 alpine 的根文件系统
└─────────────────────────┘由此引出三个关键推论:
- 复用:拉取新镜像时,如果本地已有相同的层,只下载缺失的层。多个镜像共用基础层,磁盘占用大幅下降。
- 构建缓存:Dockerfile 构建时层未变化则直接复用缓存(第 6 章详述)。
- 写时复制(copy-on-write):容器运行时并不复制整个镜像,而是在镜像之上叠加一层可写层;首次写入某个文件时才复制该文件到可写层。所以容器启动快、内存省,且多个容器共享同一镜像的只读层。
查看镜像的分层:
bash
docker history nginx:alpine输出示例:
text
IMAGE CREATED CREATED BY SIZE
a5a2e0f9e2a4 2 weeks ago /bin/sh -c #(nop) CMD ["nginx" "-g" "daemon off;"] 0B
... ... ... ...SIZE 为 0B 的层通常是纯元数据层(ENV、CMD、EXPOSE 等指令)。
第 1 章练习 3 的答案就在这:两个容器共享镜像的只读层,各自的改动写在自己独立的可写层里,互不可见——容器删除时它的可写层也随之消失,镜像不受任何影响。
4. 镜像的导出与导入
离线环境或跨机器迁移镜像,有两对命令,别用混:
| 场景 | 导出 | 导入 |
|---|---|---|
| 迁移镜像(保留分层与元数据) | docker save -o nginx.tar nginx:alpine | docker load -i nginx.tar |
| 迁移容器文件系统(扁平化为单层,丢历史) | docker export -o fs.tar <容器> | docker import fs.tar <新镜像:tag> |
bash
# 方式一:save / load(推荐,保留分层结构与历史)
docker save -o nginx.tar nginx:alpine
# 删除后 load 恢复,镜像分层与历史完整保留
docker rmi nginx:alpine && docker load -i nginx.tar
docker images | Select-String nginx
# 方式二:export / import(把正在运行的容器当前文件系统导出为单层镜像)
docker run -d --name tmp-nginx nginx:alpine
docker export -o fs.tar tmp-nginx
docker import fs.tar flat-nginx:v1
docker rm -f tmp-nginxsave/load 与 export/import 的区别:前者搬运的是镜像(分层、标签、历史都在,可离线 pull/push);后者搬运的是容器文件系统的快照(扁平一层,通常用于急救或把容器改装成镜像,一般不建议作为常规手段)。
5. 镜像仓库
Docker Hub 是默认公共仓库,拉取无需登录,推送需要账号。发布镜像的流程:
bash
docker login # 输入 Docker Hub 用户名密码
docker tag myapp:v1 yourname/myapp:v1 # 打上带账号前缀的标签
docker push yourname/myapp:v1自建私有 registry(局域网/离线内网):
bash
docker run -d --name registry -p 5000:5000 registry:2
docker tag nginx:alpine localhost:5000/nginx:alpine
docker push localhost:5000/nginx:alpine # 推送到本地私有仓库
docker pull localhost:5000/nginx:alpinelocalhost:5000 这种"带端口的前缀"会让 Docker 把它识别为 registry 地址而非命名空间。生产环境常用 Harbor 等带鉴权与漏洞扫描能力的仓库。
常见误区
- 误区:
latest标签 = 最新稳定版。 正确:latest只是"未指定标签时默认的标签",镜像作者想指向谁就指向谁。生产环境务必固定版本号(如nginx:1.27)。 - 误区:
docker rmi删不掉就是镜像坏了。 正确:多半是还有容器引用它,docker ps -a找到并先删容器。 - 误区:在容器里装的东西会保存进镜像。 正确:容器内的改动只存在于可写层,容器一删就没了;要用它得重新构建(或
docker commit,但那是反模式)。 - 误区:
export和save差不多。 正确:save是镜像(分层+历史),export是容器文件系统快照(扁平单层)。
小结
- 镜像 = 多层只读文件系统 + 元数据,来源:公共/私有仓库、自己构建。
- 完整镜像名 =
[registry/]命名空间/名字:标签,生产环境固定版本号,别依赖latest。 - 分层带来复用、构建缓存、写时复制三大收益;容器改动只进可写层,删除即消失。
- 迁移镜像用
save/load;export/import处理的是容器文件系统快照。 - 推送/拉取私有仓库:
docker login→docker tag带仓库前缀 →docker push。
练习
- 本机已有
nginx:latest与nginx:alpine,用docker history对比两者的层结构差异(提示:观察基础层与镜像体积差异,思考 alpine 为什么小)。 - 用
docker tag给 nginx 打两个标签,rmi掉其中一个,观察docker images中该镜像(IMAGE ID 相同)是否还在。 - 用
docker save把 nginx 导出为 tar,删除镜像后再docker load恢复,确认docker images内容一致。