Docker 镜像(Image)完全入门指南
从 Layer 到 Registry一文搞懂镜像的构建、存储与分发。一、为什么需要 Docker 镜像假设你在自己的电脑上写好了一个 Go 项目现在要把它部署到另一台 Linux 服务器上。你可能会遇到一连串问题代码怎么传过去Go 环境要不要重新装项目依赖要不要重新装配置文件要不要重新配如果每换一台机器都要从头来一遍部署会变得非常烦琐而且容易因为环境差异导致我电脑上能跑啊的经典问题。Docker 的解法很简单把运行环境和应用程序一起打包。传统方式 vs Docker 方式传统方式把代码搬过去 → 装运行环境 → 装依赖 → 配置环境 → 启动程序Docker 方式把应用 运行环境 依赖 配置打包成一个Image镜像→ 拿到任意机器上直接运行一句话理解Docker 镜像就是一个包含运行应用所需一切代码、依赖、配置、系统工具的只读模板。换一台机器把镜像带过去基于镜像直接启动就行。二、Image 和 Container 的关系很多新手容易混淆 Image 和 Container这里用一个类比概念类比特点Image镜像类Class 或 CD 光盘只读、不可修改Container容器实例Instance或播放出来的音乐可写、可运行具体来说Docker Image只读模板 │ │ docker run ↓ Docker Container运行实例Image只读的静态模板包含应用、依赖、文件系统和配置。它本身不会运行。Container在 Image 之上加上一层可写层Writable Layer和运行时配置Runtime Config真正把程序跑起来。一个常见问题启动 10 个 nginx 容器需要存 10 份 nginx 镜像吗不需要。多个容器共享同一份 Image 的只读内容。每个容器只是在共享的只读层上叠加自己的可写层互不干扰。这就是 Docker 高效利用磁盘空间的秘诀之一。三、镜像的分层机制Layer3.1 问题如果把整个镜像存成一个文件会怎样假设你的镜像里包含基础系统文件应用依赖配置文件应用程序本身如果只是改了一个配置文件就要整个镜像重新打包、重新存储、重新传输——时间和网络资源都浪费了。3.2 Docker 的答案Layer层Docker 没有把镜像存成一个整体而是采用了分层机制。每一层Layer记录一组文件系统变化┌──────────────────┐ │ Layer 4 │ ← 添加应用文件 ├──────────────────┤ │ Layer 3 │ ← 添加配置 ├──────────────────┤ │ Layer 2 │ ← 新增 / 修改文件 ├──────────────────┤ │ Layer 1 │ ← 基础系统文件 └──────────────────┘Layer 的本质不是应用层、依赖层这种概念划分而是文件系统变化增、改、删的集合。每一层可以包含新增文件、修改文件、删除文件。3.3 Layer 的最大好处共享和复用假设你有两个镜像Image A: Image B: ┌─────────┐ ┌─────────┐ │ Layer A3│ │ Layer B3│ ├─────────┤ ├─────────┤ │ Layer A2│ ←共享→ │ Layer A2│ ├─────────┤ ├─────────┤ │ Layer A1│ ←共享→ │ Layer A1│ └─────────┘ └─────────┘Image A 和 Image B 共用 Layer A1 和 Layer A2本地只需要存一份不需要重复存储拉取镜像时本地已有的 Layer 也不需要重新下载核心理解Layer 共享是 Docker 镜像高效存储和分发的根基。四、内容寻址Digest摘要4.1 怎么判断两个 Layer 是不是一样的Docker 使用SHA256哈希算法对 Layer 内容做摘要生成一个唯一的DigestLayer 内容 ──→ SHA256 哈希 ──→ sha256:abc123...规则很简单Digest 相同 → 内容相同 → 可以复用内容变化 → Digest 一定变化4.2 Tag vs Digest对比维度TagDigest本质名字 / 引用指针内容摘要指纹是否可变可以移动指向如latest可以被重新指向新镜像内容变才变内容不变则永远不变示例nginx:latestsha256:3b7a...用途方便人类引用精确指定内容确保一致性一句话总结Tag 像个名字可以改口叫别人Digest 像个指纹是谁就是谁无法改变。生产环境中如果要求精确部署建议使用 Digest 而不是latestTag。五、镜像清单Manifest清单文件5.1 Docker 怎么知道一个镜像由哪些 Layer 组成答案是Manifest清单文件。它是一个描述文件大概长这样Image Manifest ├── Config │ └── Config Digest → 指向镜像配置环境变量、入口命令等 └── Layers ├── Layer 1 Digest → 指向第 1 层文件 ├── Layer 2 Digest → 指向第 2 层文件 └── Layer 3 Digest → 指向第 3 层文件Manifest 的职责不负责保存文件内容只负责描述镜像由什么组成Config Layers 的 Digest 列表还包含每层的 Size 和 Media Type 等元信息5.2 实际结构以 nginx:latest 为例nginx:latestTag │ ↓ Image Manifest │ ├── Config ──→ Config JSON镜像配置 │ └── Layers ──→ Layer 1 Blob文件系统数据 → Layer 2 Blob → Layer 3 Blob拉取镜像的流程就是Tag → 找到对应 Manifest → Manifest 描述组成 → 按 Digest 定位并下载 Config Layers六、镜像是怎么构建出来的Dockerfile → Image6.1 Dockerfile 是什么Dockerfile 是一个文本文件描述镜像的构建步骤。一个典型的 Go 项目 DockerfileFROM golang:1.25 # 以哪个镜像为基础 WORKDIR /app # 设置工作目录 COPY go.mod go.sum ./ # 复制依赖描述文件 RUN go mod download # 下载依赖 COPY . . # 复制源代码 RUN go build -o app # 编译6.2 从 Dockerfile 到 ImageDockerfile ──→ docker build ──→ Layers Config ──→ Manifest ──→ Image构建过程中RUN / COPY / ADD这类指令 → 产生文件系统变化 →生成新 LayerENV / CMD / EXPOSE这类指令 → 不产生文件变化 →写入 Config元数据核心Dockerfile 描述怎么构建Build 过程生成由什么组成Layers Config最终打包成 Image。七、构建缓存Build CacheLayer 除了节省存储和网络传输还有一个非常重要的价值构建缓存。7.1 缓存的原理看这段 DockerfileCOPY go.mod go.sum ./ RUN go mod download # ← 这步很慢每次下载依赖 COPY . . RUN go build -o app # ← 这步也很慢每次编译如果合理排序go.mod 没变 → 依赖就没变 → COPY go.mod / RUN go mod download 这两层直接复用缓存跳过 → 节省大量下载依赖的时间 源代码变了 → COPY . . 这层变了 → 后面的 RUN go build 必须重新执行7.2 缓存失效规则关键规则某一层发生变化它之后的所有层都必须重新构建。这就是为什么 Dockerfile 的指令顺序非常重要推荐写法把不容易变的操作放在前面如安装系统依赖把经常变的操作放在后面如复制源代码。# ✅ 好的写法变化频率从低到高 FROM node:20-alpine WORKDIR /app COPY package.json package-lock.json ./ # 依赖文件不常变 RUN npm ci # 利用缓存 COPY . . # 源码经常变放最后 RUN npm run build# ❌ 不推荐的写法COPY . . 放前面后面全白缓存 FROM node:20-alpine WORKDIR /app COPY . . # 源码一变后面缓存全失效 RUN npm ci RUN npm run build八、多阶段构建Multi-stage Build8.1 问题构建环境 ≠ 运行环境传统方式下构建环境和运行环境是同一个┌──────────────────────────────────────┐ │ 运行环境 构建环境 │ │ Go Compiler 源码 依赖 构建工具 │ └──────────────────────────────────────┘程序已经编译好了运行时还需要编译器吗不需要。但传统方式把这些冗余的东西全打包进了镜像导致镜像臃肿。8.2 解决方案Multi-stage BuildBuild Stage构建阶段 Runtime Stage运行阶段 ┌──────────────────────┐ ┌──────────────────┐ │ Compiler │ │ │ │ Source Code │ COPY │ Application │ │ Dependencies │ ──────→ │ Binary only │ │ Build Tools │ --from │ │ │ │ │ 最小运行环境 │ │ ↓ 编译 │ │ │ │ Application Binary │ │ │ └──────────────────────┘ └──────────────────┘示例 Dockerfile# Build Stage FROM golang:1.25 AS build WORKDIR /src COPY . . RUN go build -o /app # Runtime Stage FROM alpine:3.20 COPY --frombuild /app /app ENTRYPOINT [/app]8.3 多阶段构建的价值✅ 最终镜像极小只包含运行时需要的二进制文件✅ 不携带编译器、源码、构建工具✅ 运行环境更精简、更安全减少攻击面✅ 构建依赖和运行依赖完全解耦核心Build Stage 负责构建Runtime Stage 负责运行。两者分离各司其职。九、多平台支持Multi-platform9.1 问题同一个nginx:latest在 Intel 和 ARM 机器上怎么都能用Docker 通过Image Index镜像索引支持多平台nginx:latestTag │ ↓ Image Index镜像索引 │ ├── amd64 Manifest → Configamd64 Layersamd64 └── arm64 Manifest → Configarm64 Layersarm649.2 docker pull 时发生了什么docker pull nginx │ ├── 1. 检测当前机器的 CPU 架构amd64 还是 arm64 ├── 2. 获取 Image Index ├── 3. 在 Index 中选择匹配当前平台的 Manifest └── 4. 下载对应平台的 Config Layers9.3 Multi-stage 和 Multi-platform 的区别对比维度Multi-stage BuildMulti-platform解决的问题构建问题怎么构建平台问题给谁运行结构Build Stage → Runtime Stageamd64 与 arm64 分别独立关注点怎么把镜像做小怎么覆盖不同 CPU 架构一句话Multi-stage 解决怎么构建Multi-platform 解决给谁运行。十、镜像的存储和分发Registry10.1 什么是 RegistryRegistry注册中心是 Docker 镜像的存储和分发中心。最常用的公共 Registry 是Docker Hubdocker.io。10.2 Image Reference镜像引用的结构docker.io / library / nginx : latest │ │ │ │ Registry Namespace Repo Tag 仓库地址命名空间仓库名版本引用Registrydocker.io默认、gcr.ioGoogle、私有仓库地址等Namespacelibrary官方镜像、你的用户名等Repositorynginx、my-go-app等Taglatest、1.29、1.28等也可以用sha256:xxx精确指定 Digest10.3 镜像的上传和下载我的电脑 ── docker push ──→ Registry ── docker pull ──→ 另一台服务器CLI 拉取示例dockerpull nginx# 等同于 nginx:latest不写 Tag 默认 latestdockerpull nginx:1.29# 指定 Tagdockerpull nginxsha256:xxxx# 用 Digest 精确指定推荐用于生产环境省略规则不写 Registry → 默认使用docker.io不写 Tag → 默认使用latest使用sha256:xxx→ 精确指定内容不受 Tag 移动影响十一、一次完整的 docker pull 过程当你执行docker pull nginx时背后发生了什么docker pull nginx │ ├── ① 解析 Referencenginx → 补全为 docker.io/library/nginx:latest │ ├── ② 请求 Registry获取 Image Index或直接获取 Manifest │ ├── ③ 检测当前平台的 CPU 架构选择匹配的 Manifest │ ├── ④ 从 Manifest 中获取 Config 和 Layers 的 Digest 列表 │ ├── ⑤ 检查本地是否已有相同 Digest 的 Layer │ ├── 已有 → 直接复用跳过下载 │ └── 没有 → 从 Registry 下载 │ └── ⑥ 所有 Layer 就绪 → 组装成 Local Image → docker run 启动容器关键理解pull 不一定会重新下载所有 Layer。只要本地已经有相同 Digest 的 Layer就直接复用。这得益于前面的分层机制和内容寻址设计。十二、镜像清理用久了本地可能会积累很多不再使用的镜像。如何清理12.1 悬空镜像Dangling Image定义没有任何 Tag 指向它并且没有被其他镜像引用的镜像。这类镜像通常是重复 build 同一个 Tag 时产生的前任镜像新镜像拿走了 Tag旧镜像失去了 Tag 变成了none:none。# 查看悬空镜像dockerimages-fdanglingtrue# 只清理悬空镜像安全操作dockerimage prune12.2 清理所有未使用的镜像# 删除所有没有被任何容器使用的镜像包括有 Tag 的# ⚠️ 执行前请确认dockerimage prune-a⚠️ 注意none标签的镜像不一定是 Dangling Image。它也有可能是一个中间层镜像构建缓存的一部分。只有没有任何 Tag 指向它 没有被其他镜像引用才是 Dangling。十三、完整流程总结一张图看懂 Docker Image 的完整生命周期Dockerfile │ │ docker build ↓ Layers Config │ ↓ Manifest清单文件 │ ↓ Image镜像 │ │ docker push ↓ Registry镜像仓库 │ │ docker pull ↓ Local Image本地镜像 │ │ docker run ↓ Container运行中的容器 │ │ 不再使用 ↓ docker image prune清理十四、核心概念速查表概念一句话解释类比Image运行应用所需的只读模板CD 光盘ContainerImage 的运行实例带可写层用 CD 播放出来的音乐Layer文件系统变化的集合增/改/删一层层叠加的透明胶片Digest内容的 SHA256 指纹内容变则指纹变人的指纹Tag镜像的可变别名 / 引用指针人的名字可以换Manifest镜像的组成清单记录 Config Layers 的 Digest产品的 BOM 物料清单Registry镜像的存储与分发中心云盘 / 文件服务器Dockerfile描述镜像构建步骤的脚本菜谱Build Cache利用 Layer 复用加速构建做菜时复用已切好的配菜Multi-stage构建环境和运行环境分离工厂生产车间 ≠ 用户手中产品Multi-platform同一镜像支持不同 CPU 架构同一款 App 出 iOS 版和 Android 版Dangling Image被新镜像顶替后无人引用的旧镜像换了新手机号后废弃的旧 SIM 卡十五、记住这四点就够了如果要提炼 Docker Image 最核心的设计思想就四个关键词分层Layer—— 镜像不是一整块而是叠加的文件系统变化支持高效存储和复用内容寻址Digest—— 通过 SHA256 哈希唯一标识内容内容相同即可复用清单描述Manifest—— 描述镜像由哪些 Layer 组成索引即分离仓库分发Registry—— 镜像的存储和分发中心push 上去、pull 下来理解这四点Docker Image 的整个机制就通了。