RUN 是什么
RUN 在构建阶段执行命令并把结果固化成一层文件系统。安装依赖、编译代码、调整目录属主都靠它。它有两种书写形式:一种经过命令解释器,便于使用管道与变量;另一种直接调用可执行文件,更可控、不需要解释器参与。
基本信息
| 作用 | 在构建期执行命令,结果写入镜像层 |
|---|---|
| 解释器形式 | 命令字符串交给默认解释器执行,可用管道、通配符与变量展开 |
| 直接调用形式 | 写成参数数组,直接调用可执行文件,不经过解释器 |
| 层级影响 | 每条 RUN 生成一层,命令越多层越多,体积与缓存粒度都受影响 |
| 常见配套 | 配合换行与容错参数,把安装、配置、清理放在同一条里 |
典型使用场景
- 安装系统依赖:包管理器更新索引并安装运行所需的库与工具
- 编译应用:拉取依赖、执行构建、产出可执行文件或前端静态资源
- 调整文件属性:创建应用目录并设置属主,让运行用户可写
- 生成配置或密钥:在构建期把默认配置与脚本放进镜像的约定位置
- 清理构建痕迹:删掉包管理器的索引与临时文件,减小镜像体积
两种形式与常见写法
两种形式与常见写法
RUN apt-get update && apt-get install -y --no-install-recommends curl ca-certificates
RUN set -eux; apt-get update; apt-get install -y --no-install-recommends curl; rm -rf /var/lib/apt/lists/*
RUN ["go", "build", "-o", "/out/app", "."] # 直接调用,不经解释器
RUN mkdir -p /app && chown 1000:1000 /app # 建目录并调整属主
RUN npm ci --omit=dev # 按锁文件安装依赖两种书写形式对照
| 对比项 | 解释器形式 | 直接调用形式 |
|---|---|---|
| 写法 | 一整条命令字符串 | 参数数组 |
| 变量展开 | 会展开,可用环境变量 | 不展开,变量原样传给程序 |
| 管道与通配符 | 可用 | 不可用 |
| 信号与参数传递 | 多一层解释器,需要留意行为 | 直接到达目标程序,更可控 |
| 推荐场景 | 安装依赖、需要串联命令 | 编译与调用固定程序 |
写 RUN 的实用原则
- 安装与清理放同一条:上一层删掉的文件仍占体积,分开写等于没清理
- 变更频率低的操作放前面,让后面的步骤尽量复用缓存
- 命令开头加容错与打印参数,中间命令失败会立刻中止并留下线索
- 避免在同一条里塞过多无关命令,局部改动会触发整层重建
- 不要写入日期、随机数或临时文件,会让该层缓存永远无法命中
常见失败与处理方向
- 包索引过期导致安装失败:更新索引与安装要同一条执行,或先刷新再装
- 找不到某个系统库:确认基础镜像与目标发行版的包名差异
- 编译时缺少工具链:把编译阶段放到带完整工具链的镜像里
- 权限被拒:以默认用户构建时无法写系统目录,需要时切换身份或调整目录
- 构建变慢:检查是否每次都在下载依赖,考虑使用缓存挂载或分层策略
每条执行指令都会留下一层历史。合并命令能减少层数,但过度合并会让局部改动触发整层重建,按变更频率分组是更实用的折中。
构建期的缓存命中的判断依据是命令文本与相关文件内容。命令文本里带日期或随机值,会让这一层以及它后面的所有层每次都重新执行。