一、Dockerfile核心原理:基于UnionFS分层构建
1.构建机制
docker build 逐条解析Dockerfile指令,每条指令生成一个镜像层(Layer),下层只读,上层增量;
Docker内置构建缓存机制:
- 从上往下逐条执行,指令不变、依赖文件无变化,复用本地缓存层,不重新构建;
- COPY/ADD会校验文件checksum,文件改动则缓存失效,后续全部重新构建。
本质:每一条RUN/COPY/ADD/ENV都是一次镜像提交commit,堆叠成最终镜像。
2.构建整体流程
- docker客户端把Dockerfile+上下文目录(context)打包发送dockerd;
- dockerd初始化基础镜像层(From);
- 按顺序执行指令,生成新镜像层;
- 最后生成镜像配置层(CMD/ENTRYPOINT/EXPOSE等元数据,不生成存储层)。
上下文误区:.dockerignore剔除无用文件,减少打包传输体积,加快build。
二、常用指令分层规则
| 指令 | 分层规则 | 说明 |
| FROM | 第一层 | 基础镜像,所有层的lower底层 |
| RUN | 生成新层 | 执行shell命令,写入容器upper,产生数据变更层 |
| COPY/ADD | 生成新层 | 复制宿主机文件进镜像,文件变化缓存失效 |
| ENV/ARG | 元数据层 | 环境变量,仅修改镜像配置,不占用存储层 |
| WORKDIR/USER | 元数据 | 仅修改运行配置,无数据写入 |
| EXPOSE | 元数据 | 仅声明端口,不实际打开端口 |
| CMD/ENTRYPOINT | 元数据 | 容器启动命令,镜像配置,无分层存储 |
关键:只有会修改文件系统的指令才产生存储层,配置类指令只改镜像json信息。
三、镜像臃肿根源&优化方案(生产高频)
1.坑1:多条RUN拆分,产生多余分层
错误写法:
RUN yum install -y gcc
RUN yum install -y make
RUN rm -rf /var/cache/yum/*
产生3层:安装gcc层、安装make层、清理缓存层,缓存文件依然存在前两层,镜像变大。
正确写法(同一条RUN合并,安装+清理在同一层):
RUN yum install -y gcc make \
&& rm -rf /var/cache/yum/*
安装和清理在同一个读写层,临时文件直接在本层删除,不会遗留到镜像。
2.坑2:构建依赖与运行依赖共存(多阶段构建Multi-stage重点)
场景:编译Java/Golang项目,编译需要gcc/jdk/maven,运行只需要JRE/二进制程序。
多阶段构建:编译环境在第一阶段镜像,只拷贝产物到最终镜像,抛弃编译依赖。
示例Go多阶段:
#阶段1:编译镜像(临时镜像,不会打包进最终产物)
FROM golang:1.21 AS build
WORKDIR /app
COPY . .
RUN CGO_ENABLED=0 go build -o app
#阶段2:最终运行镜像(空alpine,仅拷贝二进制)
FROM alpine:3.19
COPY --from=build /app/app /usr/local/bin/
CMD ["/usr/local/bin/app"]
优势:最终镜像只有几十MB,不带编译器、源码、依赖包。
3.坑3:COPY多余文件,利用.dockerignore
# .dockerignore
.git/
node_modules/
*.log
README.md
排除构建无用资源,缩小build上下文,避免多余文件打入镜像层。
4.坑4:指令顺序不合理,缓存频繁失效
原则:易变指令放后面,固定指令放前面
固定:FROM、ENV、RUN安装依赖
易变:COPY源码
错误:先COPY源码再装依赖→每次改源码全量重新构建
正确:先安装依赖→后COPY源码,依赖不变可复用缓存。
FROM centos
RUN yum install -y nginx 固定前置,缓存常驻
COPY ./html /usr/share/nginx/html 频繁改动后置
四、ENTRYPOINT与CMD区别(面试必考点)
- CMD:容器默认启动参数,可被
docker run 新命令直接覆盖
CMD ["nginx","-g","daemon off;"]
docker run xxx ls -l → 执行ls,替换原CMD
2. ENTRYPOINT:固定入口脚本,CMD变为ENTRYPOINT的参数
ENTRYPOINT ["/start.sh"]
CMD ["default"]
docker run xxx abc → /start.sh abc
五、.dockerignore作用
build打包上下文时忽略匹配文件,1.缩短构建传输时间;2.避免机密文件、无用依赖打包进镜像。
关联文档
- Docker 联合文件系统 — 镜像分层原理
- ../programming/脚本与编程 — Dockerfile 编写