共计 5739 个字符,预计需要花费 15 分钟才能阅读完成。
文章目录[显示]

你是否好奇过:
- 场景 1 :为什么下载一个 Ubuntu 镜像只需要几十秒,而安装一个真实的 Ubuntu 系统要几十分钟?
- 场景 2 :为什么多个容器可以共享同一个镜像,却互不影响?
- 场景 3 :为什么修改容器后重启,所有改动都消失了?
如果这些问题也困扰着你,那么今天要介绍的 Docker 镜像 将为你揭开容器技术的核心秘密。
今天这篇文章,我将用最简单的语言,带你理解 Docker 镜像的本质和分层原理。读完后,你将彻底明白容器为什么如此轻量和高效。
什么是 Docker 镜像?
一句话解释
Docker 镜像是一个 只读的文件系统模板,包含了运行应用所需的所有内容:代码、运行时、库、环境变量和配置文件。
打个比方 📸
让我用一个日常例子来解释:
Docker 镜像就像手机系统的 " 备份文件 ":
- 镜像 = 手机的完整备份(包含系统、应用、设置)
- 容器 = 从备份恢复出来的手机实例
- 镜像分层 = 备份时只保存变化的部分,节省空间
你可以从同一个备份(镜像)恢复出多个手机(容器),每个手机可以独立使用,互不干扰。更重要的是,备份文件本身是只读的,不会因为手机使用而改变。
对比理解 ⚖️
| 传统虚拟机镜像 | Docker 镜像 |
|---|---|
| 包含完整操作系统(几 GB) | 只包含应用和依赖(几十 MB) |
| 启动需要几分钟 | 启动只需几秒 |
| 每个镜像独立存储 | 共享公共层,节省空间 |
| 难以版本管理 | 每层都有版本记录 |
| 分发困难 | 可轻松分享和下载 |
核心特点
- 只读性:镜像一旦构建完成就不可修改,保证了一致性
- 分层结构:像千层蛋糕一样,每层都是独立的文件系统
- 复用性:多个镜像可以共享相同的底层,节省存储空间
Docker 镜像是如何工作的?
简化模型
让我们看看镜像和容器的关系:
Docker 镜像(只读)┌─────────────────────┐
│ 应用层 (App) │ ← 最上层:你的应用
├─────────────────────┤
│ 依赖层 (Deps) │ ← 中间层:运行时依赖
├─────────────────────┤
│ 基础层 (Base OS) │ ← 底层:基础操作系统
└─────────────────────┘
↓ docker run
┌─────────────────────┐
│ 可写层 │ ← 容器的修改都在这里
├─────────────────────┤
│ 应用层 (只读) │
│ 依赖层 (只读) │
│ 基础层 (只读) │
└─────────────────────┘
运行中的容器
工作流程
- 拉取镜像:从 Docker Hub 或其他仓库下载镜像到本地
- 创建容器:Docker 在镜像的只读层之上添加一个可写层
- 运行应用:容器中的所有修改都发生在可写层,不影响原镜像
- 停止容器:可写层保留,重启容器时恢复状态
- 删除容器:可写层被删除,但镜像保持不变
关键概念
- 镜像层(Image Layer):只读的文件系统层,可以被多个容器共享
- 容器层(Container Layer):可写的顶层,存储容器运行时的所有改变
- 联合文件系统(Union FS):将多个层合并成一个统一的文件系统视图
💡 小贴士:想象镜像是一本印刷好的书(只读),而容器是在书上夹的便签纸(可写)。你可以在便签上做笔记,但不会改变原书的内容。
什么时候用 Docker 镜像?
适用场景 ✅
-
应用分发:将应用及其依赖打包成镜像
- 示例:开发了一个 Web 应用,打包成镜像后,团队成员可以直接运行,无需配置环境
-
环境一致性:确保开发、测试、生产环境完全一致
- 示例:本地测试用的 Node.js 16 镜像,部署到服务器也是同一个镜像
-
快速部署:从镜像启动容器只需几秒
- 示例:需要快速扩容 10 个 Nginx 实例,从同一个镜像启动即可
-
版本管理:为应用的不同版本创建不同的镜像
- 示例:应用 v1.0、v1.1、v2.0 各有一个镜像,可以随时回滚
不适用场景 ❌
- 需要持久化数据:镜像是只读的,数据应该存储在卷中
- 频繁修改的内容:不要把经常变化的配置硬编码在镜像里
- 敏感信息:密码、密钥不应该写入镜像(会被永久记录)
💡 判断标准:如果某个文件或配置在应用运行过程中需要修改,就不应该写入镜像,而应该通过卷挂载或环境变量传入。
5 分钟快速体验
让我们通过实际操作,感受 Docker 镜像的拉取和分层结构。
前置要求
- 已安装 Docker(参考第 2 天文章)
- 确保 Docker 服务正在运行
操作步骤
第 1 步:搜索镜像
# 在 Docker Hub 上搜索 nginx 镜像
docker search nginx
预期结果:
NAME DESCRIPTION STARS OFFICIAL
nginx Official build of Nginx. 19000 [OK]
linuxserver/nginx An Nginx container... 180
bitnami/nginx Bitnami nginx Docker Image 150
第 2 步:拉取镜像
# 拉取官方 nginx 镜像(默认拉取 latest 标签)docker pull nginx
预期结果:
Using default tag: latest
latest: Pulling from library/nginx
a2abf6c4d29d: Pull complete ← 第 1 层
a9edb18cadd1: Pull complete ← 第 2 层
589b7251471a: Pull complete ← 第 3 层
186b1aaa4aa6: Pull complete ← 第 4 层
b4df32aa5a72: Pull complete ← 第 5 层
a0bcbecc962e: Pull complete ← 第 6 层
Digest: sha256:0d17b565c37bcbd895e9d92315a05c1c3c9a29f762b011a10c54a66cd53c9b31
Status: Downloaded newer image for nginx:latest
docker.io/library/nginx:latest
💡 注意:每个 "Pull complete" 代表下载了一层!
第 3 步:查看镜像详情
# 查看本地所有镜像
docker images
预期结果:
REPOSITORY TAG IMAGE ID CREATED SIZE
nginx latest 605c77e624dd 2 weeks ago 141MB
hello-world latest feb5d9fea6a5 2 years ago 13.3kB
第 4 步:查看镜像的分层历史
# 查看 nginx 镜像的构建历史
docker history nginx
预期结果:
IMAGE CREATED CREATED BY SIZE COMMENT
605c77e624dd 2 weeks ago /bin/sh -c #(nop) CMD ["nginx" "-g" "daemon… 0B
2 weeks ago /bin/sh -c #(nop) STOPSIGNAL SIGQUIT 0B
2 weeks ago /bin/sh -c #(nop) EXPOSE 80 0B
2 weeks ago /bin/sh -c #(nop) ENTRYPOINT ["/docker-entr… 0B
2 weeks ago /bin/sh -c set -x && addgroup --system -… 61.1MB
2 weeks ago /bin/sh -c #(nop) ENV PKG_RELEASE=1~bookworm 0B
2 weeks ago /bin/sh -c #(nop) ENV NV_VERSION=1.24.0 0B
2 weeks ago /bin/sh -c #(nop) LABEL maintainer=NGINX Do… 0B
2 weeks ago /bin/sh -c #(nop) CMD ["bash"] 0B
2 weeks ago /bin/sh -c #(nop) ADD file:d261a6f6921acdd33… 74.8MB
每一行代表一层,SIZE 列显示该层的大小。
第 5 步:体验镜像分层的复用
# 拉取另一个基于 nginx 的镜像
docker pull nginx:alpine
预期结果:
alpine: Pulling from library/nginx
4abcf2066143: Already exists ← 这层已存在,直接复用!8e7ac6c8107e: Pull complete ← 只下载不同的层
96e8cbaf4e7e: Pull complete
Digest: sha256:a5127daff3d6f4606be3100a252419bfa84fd6ee5cd74d0feaca1a5068f97dcf
Status: Downloaded newer image for nginx:alpine
💡 关键发现:"Already exists" 说明该层在之前下载 nginx:latest 时已经存在,Docker 智能复用了这一层!
运行效果
现在你已经:
- ✅ 学会了搜索和拉取镜像
- ✅ 看到了镜像的分层结构
- ✅ 体验了层的复用机制
🎉 恭喜!你已经掌握了 Docker 镜像的基本操作。
深入理解镜像分层
为什么要分层?
Docker 镜像采用分层结构有三大好处:
-
节省存储空间 💾
- 多个镜像可以共享相同的基础层
- 例如:10 个基于 Ubuntu 的镜像,Ubuntu 层只存储一份
-
加快传输速度 🚀
- 只下载改变的层,已存在的层直接复用
- 推送镜像时,也只上传新增的层
-
提高构建效率 ⚡
- 构建镜像时,未改变的层使用缓存
- 只需重新构建改变的层及其之后的层
分层的实际例子
假设我们有这样一个 Dockerfile:
FROM ubuntu:20.04 # 层 1:基础系统 (80MB)
RUN apt-get update # 层 2:更新包列表 (50MB)
RUN apt-get install -y python3 # 层 3:安装 Python (100MB)
COPY app.py /app/ # 层 4:复制应用代码 (1KB)
CMD ["python3", "/app/app.py"] # 层 5:设置启动命令 (0B)
存储结构:
┌─────────────────────┐
│ CMD (0B) │ ← 层 5:元数据
├─────────────────────┤
│ app.py (1KB) │ ← 层 4:应用代码
├─────────────────────┤
│ python3 (100MB) │ ← 层 3:Python 环境
├─────────────────────┤
│ apt-get update(50MB)│ ← 层 2:包索引
├─────────────────────┤
│ ubuntu:20.04 (80MB) │ ← 层 1:基础系统
└─────────────────────┘
总大小:约 230MB
复用场景:
- 如果你构建另一个基于
ubuntu:20.04的镜像,层 1 直接复用(节省 80MB) - 如果只修改了
app.py,重新构建时只需重建层 4 和层 5(节省几分钟)
查看层的详细信息
# 使用 inspect 命令查看镜像的层信息
docker inspect nginx | grep -A 10 "Layers"
预期输出:
"Layers": [
"sha256:e1caac4eb9d2ec3c6c3d0e88e8e3c5c6cf7c3a2e3d6c1e2d3c4b5a6e7d8c9d0e",
"sha256:a2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3",
"sha256:b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4"
]
每个 sha256 哈希值代表一个不可变的层。
🤔 常见疑问
Q1:镜像删除后,容器还能运行吗?
A:可以!容器运行时已经将需要的层加载到内存,删除镜像不影响正在运行的容器。但如果容器停止后再启动,就会报错。
Q2:为什么 docker images 显示的大小和实际占用空间不一样?
A:因为多个镜像共享了某些层。使用 docker system df 可以查看实际占用空间。
Q3:如何清理不用的镜像?
A:使用 docker image prune 清理悬空镜像(没有标签的镜像),使用 docker image prune -a 清理所有未使用的镜像。
Q4:镜像的 latest 标签是什么意思?
A:latest 只是一个默认标签,并不一定是最新版本。在生产环境中,应该使用具体的版本号(如 nginx:1.24.0)而不是 latest。
Q5:如何查看某个镜像占用了多少空间?
A:使用 docker images 查看单个镜像大小,或使用 docker system df -v 查看详细的空间使用情况。
📚 本文总结
今天我们深入学习了 Docker 镜像,让我们快速回顾一下要点:
- 核心概念:Docker 镜像是一个只读的文件系统模板,包含运行应用所需的一切
- 分层结构:镜像采用分层设计,每层独立且可复用,节省空间和时间
- 工作原理:容器在镜像的只读层之上添加可写层,所有修改都发生在可写层
- 实际应用:通过
docker pull、docker images、docker history等命令管理镜像
下一步学习建议
掌握了 Docker 镜像的基础知识后,建议你:
- 实践:亲手操作一遍文章中的所有命令,观察分层结构
- 深入:关注下一篇文章《Day 5:容器生命周期管理》,学习如何从镜像创建和管理容器
- 扩展:尝试拉取不同的镜像(如
redis、mysql),观察它们的分层结构有何不同
🔗 相关文章推荐
- 上一篇:Day 3 – 第一个容器:Hello World 背后的秘密
- 下一篇:Day 5 – 容器生命周期管理:创建、启动、停止、删除
- 扩展阅读:Docker Hub 官方文档
💬 互动时间
讨论话题:你在工作中遇到过 " 开发环境正常,生产环境出问题 " 的情况吗?现在你会如何用 Docker 镜像解决这个问题?欢迎在评论区分享你的经验!
今日作业:
- 拉取 3 个不同的镜像(如
nginx、redis、python) - 使用
docker history查看它们的分层结构 - 用
docker system df查看实际占用空间,验证层复用
如果这篇文章对你有帮助,请:
- 👍 点个「赞」和「在看」
- 🔄 分享给正在学习 Docker 的朋友
- 💬 评论区告诉我你最感兴趣的镜像是哪个
📖 往期推荐
- Day 1:Docker 是什么?5 分钟看懂容器革命
- Day 2:Docker 安装指南:Windows/Mac/Linux 全平台
- Day 3:第一个容器:Hello World 背后的秘密
关于本系列
这是「Docker 30 天实战系列」的第 4 篇文章。本系列将用 30 天时间,带你从零基础到实战部署,系统掌握 Docker 容器技术。
🔔 点击关注,不错过每一篇干货!
