Dockerfile 中的 ARG 和 ENV 详解
在 Dockerfile 中,ARG 和 ENV 都用于定义变量,但它们的作用域和生命周期完全不同。理解两者的区别对编写灵活、安全的 Dockerfile 至关重要。
1. 基本定义
ARG:构建时变量,仅在docker build过程中有效。构建完成后不会保留在镜像中。ENV:环境变量,在构建时和运行时都有效。最终会作为容器内的环境变量存在。
2. 语法
dockerfile
ARG <变量名>[=<默认值>]
ENV <key>=<value> ...示例:
dockerfile
ARG VERSION=latest
ENV NODE_ENV=production \
APP_HOME=/appARG可定义默认值;如果未提供默认值,则默认为空字符串。ENV中的value会被视为字符串;如果需要使用其他变量,可以用${VAR}引用(但ENV不能跨指令引用ARG?实际上可以,但需注意顺序)。
3. 作用域与生命周期
| 特性 | ARG | ENV |
|---|---|---|
| 存在阶段 | 仅构建时 | 构建时 + 运行时 |
| 是否保留在最终镜像中 | ❌ 否 | ✅ 是(作为环境变量) |
能否被 docker run -e 覆盖 | ❌ 否(构建后消失) | ✅ 是(启动容器时可覆盖) |
能否被 docker build --build-arg 覆盖 | ✅ 是 | ❌ 否 |
| 用途 | 参数化构建(如版本号、镜像仓库) | 配置应用环境(如数据库连接、模式) |
关键点:
ARG只在FROM、RUN、COPY、ADD、WORKDIR、USER、LABEL等构建指令中生效(CMD和ENTRYPOINT中不能直接使用ARG,除非通过ENV传递)。ENV不仅影响构建过程(后续指令中的RUN等),还会作为容器的环境变量持久化。
4. 如何传递值
构建时(docker build)
为
ARG赋值:docker build --build-arg <变量名>=<值>无法直接为
ENV赋值,但可以通过ARG间接设置:dockerfileARG APP_VERSION ENV APP_VERSION=${APP_VERSION}这样构建时传入的
APP_VERSION会通过ARG接收,再赋值给ENV,最终保留在镜像中。
运行时(docker run)
- 覆盖
ENV:docker run -e <变量名>=<新值> ARG已不存在,无法覆盖。
5. 详细对比表
| 对比项 | ARG | ENV |
|---|---|---|
| 定义默认值 | ARG VERSION=1.0 | ENV VERSION=1.0 |
| 构建时引用 | 可在 RUN 等指令中用 $VERSION | 也可用 $VERSION |
| 是否影响缓存 | 当 ARG 值变化时,后续依赖它的指令会缓存失效 | 当 ENV 值变化时,后续所有指令缓存失效(因为 ENV 是层的一部分) |
| 安全性 | 适合存储构建时敏感信息?不推荐,因为 docker history 仍会显示 | 绝对不要存储敏感信息,会留在镜像中 |
| 推荐用途 | 控制构建行为(如是否启用某个功能、选择基础镜像标签) | 配置运行时应用(如 DATABASE_URL、LOG_LEVEL) |
6. 使用场景示例
场景1:动态指定基础镜像版本
dockerfile
ARG UBUNTU_VERSION=22.04
FROM ubuntu:${UBUNTU_VERSION}
RUN apt-get update && apt-get install -y curl构建时:docker build --build-arg UBUNTU_VERSION=20.04 -t myimage .
场景2:传递构建版本号并保留到镜像中
dockerfile
ARG BUILD_VERSION
ENV APP_VERSION=${BUILD_VERSION}
RUN echo "Building version ${APP_VERSION}"运行时:docker run -e APP_VERSION=2.0 myimage 可覆盖。
场景3:为不同环境构建(开发 vs 生产)
dockerfile
ARG ENV_TYPE=production
ENV NODE_ENV=${ENV_TYPE}
RUN if [ "$NODE_ENV" = "development" ]; then \
npm install --dev; \
else \
npm install --production; \
fi构建开发镜像:docker build --build-arg ENV_TYPE=development -t myapp:dev .
场景4:使用 ARG 控制缓存(避免破坏缓存)
dockerfile
ARG CACHE_BUST=1
RUN curl -fsSL https://example.com/package.tar.gz | tar xz每次构建时传入不同的 CACHE_BUST(如时间戳),强制 RUN 重新执行。
7. 最佳实践
✅ 推荐做法
优先使用
ARG控制构建行为,不污染最终镜像。将运行时配置(如数据库连接)交给
ENV,以便通过-e或--env-file灵活修改。敏感信息(密码、令牌)不要用
ENV,而应使用 Docker secrets 或运行时注入(如-e从外部传入)。使用
ARG为ENV提供默认值,兼顾构建灵活性和运行时可配置性:dockerfileARG DEFAULT_PORT=8080 ENV PORT=$DEFAULT_PORT利用
ARG破坏缓存:在需要强制刷新某层时,传入一个随机值(如--build-arg BUILD_DATE=$(date -Iseconds))。在 Dockerfile 开头声明所有
ARG,避免在FROM之后意外覆盖(ARG在FROM前声明可用于选择基础镜像)。
❌ 避免的陷阱
- 不要将
ENV用于构建专用参数,这些值会泄露到最终镜像,增加体积和暴露风险。 - 不要在
CMD或ENTRYPOINT中直接使用ARG,它不可用。必须通过ENV传递或使用 shell 脚本包装。 - 注意
ARG的作用域:每个FROM指令会重置ARG(多阶段构建时)。在多阶段中传递ARG需要重复声明。 ENV值一旦设置,后续所有层都会包含它,修改ENV会使缓存大面积失效。
8. 验证示例
Dockerfile:
dockerfile
ARG BUILD_TIME
ENV RUNTIME_VAR="default"
RUN echo "Build arg: ${BUILD_TIME}" > /build-info.txt
RUN echo "Runtime var: ${RUNTIME_VAR}" > /runtime-info.txt构建:
bash
docker build --build-arg BUILD_TIME="2025-01-01" -t test .检查:
bash
docker run --rm test cat /build-info.txt # 输出 "Build arg: 2025-01-01"
docker run --rm test cat /runtime-info.txt # 输出 "Runtime var: default"
docker run --rm -e RUNTIME_VAR="override" test printenv RUNTIME_VAR # 输出 "override"总结
ARG是构建时的“参数”,用完即弃,适合控制构建流程。ENV是环境变量,永久保留,适合配置运行环境。- 两者配合使用可实现灵活、安全的 Docker 镜像:用
ARG接收构建时输入,通过ENV传递给运行时。
