Docker基础回顾:Dockerfile基本指令及镜像分层原理

在进入多阶段构建实战之前,必须牢固掌握Dockerfile的核心指令与镜像分层机制。这两者是镜像瘦身的理论基础——不理解分层,就无法理解为什么多阶段构建能有效减小镜像体积。

2.1 Dockerfile核心指令速查

Dockerfile是一个文本文件,包含一条条构建指令。每条指令都会在镜像中创建一个新的层(Layer)。以下是生产中最常用的5条指令:

指令 作用 层数影响 典型用法
FROM 指定基础镜像,每个Dockerfile必须从FROM开始 1层(基础镜像本身可能包含多层) FROM ubuntu:22.04
RUN 在镜像中执行命令,常用于安装软件包、编译代码 每条RUN增加1层 RUN apt-get update && apt-get install -y curl
COPY 从构建上下文复制文件/目录到镜像中 每条COPY增加1层 COPY ./app /app
CMD 提供容器启动时的默认命令,可被docker run参数覆盖 不增加层(仅元数据) CMD ["python", "app.py"]
ENTRYPOINT 配置容器启动时的可执行文件,不易被覆盖 不增加层(仅元数据) ENTRYPOINT ["java", "-jar"]
最佳实践:将多条RUN命令用&&连接成一条,减少层数。例如:RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*。这比三条独立的RUN指令少两层。

2.2 CMD与ENTRYPOINT的区别

这两条指令都定义容器启动行为,但协作方式不同。理解它们的组合规则,对编写正确的Dockerfile至关重要:

Dockerfile写法 docker run附加参数 实际执行命令 说明
只有CMD 无 CMD内容 默认行为
只有CMD bash bash CMD被覆盖
ENTRYPOINT + CMD 无 ENTRYPOINT CMD CMD作为默认参数
ENTRYPOINT + CMD --debug ENTRYPOINT --debug 附加参数追加到ENTRYPOINT后
生产建议:对于后端Java/Go应用,推荐使用ENTRYPOINT固定可执行文件,CMD提供默认JVM参数或配置文件路径。这样运维人员可以通过docker run追加参数调整启动行为,而不必修改Dockerfile。

2.3 镜像分层原理

Docker镜像由一系列只读层堆叠而成。每一层代表Dockerfile中的一条指令(FROM、RUN、COPY等)。当容器运行时,Docker在镜像层之上添加一个可写容器层。

Docker镜像分层结构示意图 Docker镜像分层结构 容器层(可写) COPY app.jar RUN apt-get install FROM ubuntu:22.04 分层特点 • 层是只读的 • 每层只记录变更 • 层可被多个镜像共享 • 删除文件不会减小 镜像体积(标记删除) • 层数越多,构建和 推送越慢 • 容器层写入不影响 镜像层 镜像由多层只读层堆叠,容器启动时在最上层添加可写层 所有写操作(修改/删除/新建)都在容器层完成

2.4 分层带来的镜像体积陷阱

分层机制虽然节省了存储和网络传输,但也带来了一个常见的体积陷阱:文件删除不会减小镜像体积。

典型错误:很多开发者这样写Dockerfile:
FROM ubuntu:22.04
RUN apt-get update
RUN apt-get install -y curl
RUN apt-get remove -y curl
RUN apt-get autoremove -y
结果:镜像体积仍然很大!因为apt-get install层已经包含了curl及其依赖文件,后续的remove层只是在容器层标记删除,但镜像层中的文件依然存在。

正确的做法是:在同一层中完成安装、使用、清理:

FROM ubuntu:22.04
RUN apt-get update && \
    apt-get install -y curl && \
    # 使用curl完成某些操作
    curl -o /tmp/init.sh https://example.com/init.sh && \
    # 最后清理,所有操作在同一层
    apt-get remove -y curl && \
    apt-get autoremove -y && \
    rm -rf /var/lib/apt/lists/*

2.5 构建缓存与层复用

Docker在构建时会缓存每一层。如果Dockerfile的某条指令和缓存命中,Docker会直接使用缓存层,跳过执行。这是加速构建的关键机制,但也可能导致缓存失效问题。

Docker构建缓存命中与失效流程 Docker构建缓存命中与失效流程 缓存命中 ✅ 直接使用缓存层(跳过执行) 继续检查下一条指令的缓存 缓存失效 ❌ 重新执行该指令 后续所有指令缓存全部失效 缓存失效后,Docker会重新执行当前及后续所有指令,构建时间显著增加 优化策略:将不常变动的指令(如安装系统包)放在Dockerfile前面

2.6 缓存失效的常见原因

  • COPY/ADD文件内容变化:如果COPY的文件内容发生改变,该层缓存失效,后续所有层缓存也失效。
  • 基础镜像更新:FROM ubuntu:22.04如果拉取到新版本,缓存失效。
  • 构建上下文变化:即使Dockerfile没变,构建上下文中的文件变化也会导致COPY缓存失效。
  • RUN指令内容变化:如果RUN apt-get install -y curl改为RUN apt-get install -y wget,缓存失效。
缓存优化技巧:对于后端项目,将COPY package.json和RUN npm install放在COPY 源代码之前。这样只要package.json不变,依赖安装层就可以复用缓存,即使源代码频繁修改也不会触发依赖重新安装。

2.7 本章小结

知识点 关键要点 对镜像瘦身的意义
FROM 选择合适的基础镜像(如alpine) 基础镜像越小,最终镜像越小
RUN 合并命令、清理临时文件 减少层数,避免无用文件残留
COPY 只复制必要文件,使用.dockerignore 减少镜像层大小
分层原理 删除操作不减小体积,需在同一层清理 理解此原理才能正确瘦身
构建缓存 合理安排指令顺序,最大化缓存命中 加速构建,不影响体积但影响效率