Dockerfile 是定义容器镜像内容的文本文件,每一条指令都会让构建引擎生成一层只读文件系统。理解这些基础指令的运行机制和相互影响,是写出高效镜像的前提。很多人刚开始写 Dockerfile 只是照抄网上的片段,一旦构建变慢或镜像过大就无从下手,其实问题往往出在指令使用方式上。

FROM 与基础镜像选择
FROM 指令必须出现在 Dockerfile 的第一行(除注释外),它指定了新镜像的基础层。基础镜像决定了后续指令可用的操作系统、预装软件和包管理器。例如 FROM ubuntu:22.04 会拉取完整的 Ubuntu 根文件系统,而 FROM alpine:3.18 则是一个仅几兆字节的轻量系统。选择不同的基础镜像,会直接影响最终镜像体积和安全更新频率。
在实际项目中,如果运行的是编译型语言,通常建议使用多阶段构建,第一阶段用包含编译器的镜像,第二阶段用精简运行环境。即便只用单阶段,也应优先选择官方维护的镜像并固定版本标签,避免使用 latest 造成构建结果不可控。下面展示一个最简单的基础指定法:
# 使用官方 Node.js 18 精简版作为基础 FROM node:18-alpine # 后续指令都基于该层运行 WORKDIR /app
需要注意,FROM 可以多次出现以实现多阶段构建,每个 FROM 都开启一个新的构建阶段,之前阶段的文件不会自动带入,必须通过 COPY --from= 显式传递。这种机制让我们能把构建工具和最终运行环境彻底分离,显著减小生产镜像。
RUN 指令与层缓存原理
RUN 用于在构建过程中执行命令,比如安装系统包、编译代码或创建目录。每一条 RUN 都会提交为一个新的镜像层,因此多条命令若分开写就会产生多层。合理利用层缓存能极大加快重复构建:Docker 会按顺序检查指令及上下文,若某层之前构建过且内容未变,就直接复用缓存。
为了兼顾可读性与缓存效率,常把不常变动的安装命令放在前面,频繁改动的代码复制放在后面。同时,应在同一条 RUN 中完成安装并清理临时文件,防止缓存了无用数据。例如下面写法在 apt 安装后立刻清除索引:
RUN apt-get update
&& apt-get install -y curl ca-certificates
&& rm -rf /var/lib/apt/lists/*
如果单纯把安装和清理拆成两个 RUN,第一个层依然保留着庞大的索引文件,第二个层只是标记删除,最终镜像并不会变小。因此合并相关操作并利用连接符是减小体积的关键手段。此外,RUN 默认使用 /bin/sh -c 执行,在 Alpine 等使用 ash 的系统中应注意语法兼容。
COPY 与 ADD 的差异及文件注入
COPY 指令把构建上下文中的本地文件或目录复制到镜像内指定路径,它只做复制,不改变文件内容。与之相比,ADD 除了复制还支持自动解压压缩包和从远程 URL 拉取文件,但这种隐性行为常带来意外。官方最佳实践推荐优先用 COPY,仅在确实需要自动解压 tar 包时才用 ADD。
文件注入看似简单,但若忽略 .dockerignore 文件,可能把本地 node_modules 或 .git 目录全部打进镜像,既拖慢构建又增大体积。通过 .dockerignore 排除无关内容,再配合 COPY 精确复制,能保持上下文干净。示例配置如下:
# 仅复制源码和依赖描述 COPY package.json ./ COPY src/ ./src/ # 错误示范:ADD 会自动解压并可能访问网络 # ADD https://ipipp.com/some.tar.gz /tmp/
在权限方面,COPY 复制的文件默认属于 root,若容器以非 root 用户运行,应提前用 RUN chown 调整,或在 COPY 后切换用户。另外,复制大文件时要注意层大小,因为每一层都会被永久保留,即便后续删除也只是在新层标记隐藏,实际存储并未释放。理解这些细节才能掌控镜像的真实占用。
CMD 与 ENTRYPOINT 的启动控制
镜像构建完成后,容器启动时运行什么程序由 CMD 和 ENTRYPOINT 决定。CMD 提供默认参数,容易被 docker run 后的命令覆盖;ENTRYPOINT 则定义不可轻易替换的主命令,适合把镜像固化成特定工具。二者可组合使用,让 ENTRYPOINT 固定可执行文件,CMD 作为默认参数。
常见误区是把 RUN 里启动服务的命令和 CMD 混淆,前者在构建期就结束,后者才在运行期生效。若 CMD 写成 CMD ["npm", "start"] 这种 exec 形式,进程 PID 为 1,能正确接收信号;若写成 shell 形式,则外层多一层 shell,导致停止信号无法传递。参考写法:
ENTRYPOINT ["node"] CMD ["server.js"]
当我们需要在容器启动前做环境变量处理,可写一个小脚本作为 ENTRYPOINT,脚本最后用 exec 替换自身来启动主程序,这样既保留预处理逻辑又不丢失信号响应。掌握这两条指令的搭配,才能让镜像行为可预测,方便在编排系统中稳定运行。
Dockerfile容器镜像构建基础指令修改时间:2026-08-16 02:04:28