Docker 镜像 & Dockerfile
一、整体认知:镜像是如何诞生的?
Dockerfile → docker build → 镜像(多层只读)→ docker run → 容器(可写层)
核心结论
-
✅ Dockerfile 是"源代码"
-
✅ 镜像是"构建产物"
-
✅ 容器是"运行实例"
-
✅ 分层结构是 Docker 的灵魂
二、镜像分层结构(结合你发的图)
纯文本
纯文本
┌───────────────┐ ← 容器层(可写,临时)
│ Container │
├───────────────┤
│ CMD / ENTRY │ ← 镜像层4(不新增体积)
├───────────────┤
│ Nginx Install│ ← 镜像层3(RUN)
├───────────────┤
│ ADD Source │ ← 镜像层2(ADD)
├───────────────┤
│ Yum Install │ ← 镜像层1(RUN)
├───────────────┤
│ CentOS 7 │ ← 基础镜像层(FROM)
└───────────────┘
关键点
| 特性 | 说明 |
|---|---|
| 每层只读 | 镜像一旦构建不可更改 |
| 层可复用 | 不同镜像可共享同一层 |
| 写时复制 | 修改才复制到容器层 |
| 缓存命中 | 上层不变,下层直接复用 |
三、Dockerfile 如何驱动分层构建(逐条对照)
1️⃣ FROM(基础层)
dockerfile
dockerfile
FROM centos:7
✅ 对应:
-
镜像最底层
-
所有后续层的基础
📌 原则:
-
越小越好
-
越稳定越好
2️⃣ RUN(构建层,核心)
dockerfile
dockerfile
RUN yum install -y gcc gcc-c++ make
✅ 对应:
-
镜像层 1
-
每一条 RUN = 一个新层
📌 优化写法:
dockerfile
dockerfile
RUN yum install -y gcc make && \
yum clean all && \
rm -rf /var/cache/yum
3️⃣ ADD / COPY(文件层)
dockerfile
dockerfile
ADD nginx-1.20.tar.gz /usr/local/
✅ 对应:
-
镜像层 2
-
引入外部代码或源码
📌 规则:
-
ADD:自动解压(慎用)
-
COPY:语义清晰(推荐)
4️⃣ RUN(编译安装层)
dockerfile
dockerfile
RUN cd /usr/local/nginx && ./configure && make && make install
✅ 对应:
-
镜像层 3
-
构建期执行,不占容器运行时
5️⃣ CMD / ENTRYPOINT(运行定义层)
dockerfile
dockerfile
CMD ["nginx", "-g", "daemon off;"]
✅ 对应:
-
不生成新镜像层
-
决定容器启动行为
| 指令 | 是否可覆盖 | 场景 |
|---|---|---|
| CMD | ✅ | 默认参数 |
| ENTRYPOINT | ❌ | 固定主程序 |
四、容器运行时发生了什么?
-
Docker 从镜像复制所有只读层
-
在最上层挂载 可写容器层
-
执行 CMD / ENTRYPOINT
-
容器停止 → 可写层销毁
-
镜像始终不变 ✅
📌 这就是 环境一致性 的根本原因
五、Dockerfile 核心指令速查表(考试级)
| 指令 | 是否建层 | 作用 | 建议 |
|---|---|---|---|
| FROM | ✅ | 基础镜像 | 必写 |
| RUN | ✅ | 构建命令 | 合并写 |
| COPY | ✅ | 拷文件 | 首选 |
| ADD | ✅ | 拷+解压 | 少用 |
| CMD | ❌ | 启动命令 | 可覆盖 |
| ENTRYPOINT | ❌ | 固定入口 | 生产推荐 |
| ENV | ❌ | 环境变量 | 必用 |
| WORKDIR | ❌ | 工作目录 | 代替 cd |
| EXPOSE | ❌ | 声明端口 | 仅说明 |
| USER | ❌ | 运行身份 | 非 root |
| VOLUME | ❌ | 数据卷 | 存数据 |
六、镜像构建三大核心机制(图中红字)
✅ 1. 分层构建
-
每层对应 Dockerfile 指令
-
独立、可复用、可缓存
✅ 2. 缓存复用
-
指令不变 → 层不变 → 秒级构建
-
改一行代码 → 只重跑该行及之后
✅ 3. 写时复制(CoW)
-
镜像层共享
-
修改才复制
-
节省磁盘 & 内存
七、Dockerfile 最佳实践(实战级)
✅ 使用 .dockerignore
✅ 合并 RUN,清理缓存
✅ 变量抽成 ENV
✅ 代码放最后(利用缓存)
✅ 多阶段构建减小体积
✅ 非 root 用户运行
✅ 明确 tag,不用 latest
八、多阶段构建(进阶,但非常值得)
dockerfile
dockerfile
FROM gcc AS builder
RUN gcc main.c -o app
FROM alpine
COPY --from=builder /app /app
CMD ["/app"]