Dockerfile 写作精讲 / 05 - ENV 与 ARG
05 - ENV 与 ARG:环境变量与构建参数
5.1 指令概述
ENV 和 ARG 都用于定义变量,但它们的作用域和用途有本质区别。
| 特性 | ENV | ARG |
|---|---|---|
| 作用阶段 | 构建 + 运行时 | 仅构建阶段 |
| 持久化到镜像 | ✅ | ❌ |
docker run -e 可覆盖 | ✅ | 不适用 |
--build-arg 传入 | ❌ | ✅ |
| 默认值 | ✅ | ✅ |
| 安全性 | ⚠️ 可见于 docker inspect | ⚠️ 可见于构建历史 |
5.2 ENV 指令详解
基本语法
# 形式一:单个变量
ENV NODE_ENV=production
# 形式二:多个变量(旧语法,空格分隔)
ENV NODE_ENV=production PORT=3000
# 形式三:key=value 列表(推荐,更清晰)
ENV NODE_ENV=production
ENV PORT=3000
ENV APP_HOME=/app
ENV 的作用
FROM ubuntu:22.04
# 设置环境变量
ENV APP_HOME=/opt/myapp
ENV APP_USER=appuser
ENV PATH="${APP_HOME}/bin:${PATH}"
# 后续指令中可直接使用
WORKDIR ${APP_HOME}
RUN echo "Installing to ${APP_HOME}"
# 运行时容器中也可使用
CMD ["${APP_HOME}/bin/start.sh"]
注意:
CMD和ENTRYPOINT使用 exec 形式时,环境变量不会被替换。只有 shell 形式才会替换:# ❌ 不会替换 $APP_HOME CMD ["${APP_HOME}/bin/start.sh"] # ✅ Shell 形式会替换 CMD ${APP_HOME}/bin/start.sh # ✅ 或使用 sh -c CMD ["sh", "-c", "${APP_HOME}/bin/start.sh"]
常用环境变量
# Python
ENV PYTHONUNBUFFERED=1
ENV PYTHONDONTWRITEBYTECODE=1
ENV PIP_NO_CACHE_DIR=1
# Node.js
ENV NODE_ENV=production
ENV NPM_CONFIG_LOGLEVEL=warn
# Java
ENV JAVA_OPTS="-Xms256m -Xmx512m"
ENV JAVA_HOME=/usr/lib/jvm/java-21-openjdk
# Go(通常不需要,编译时已嵌入)
ENV GIN_MODE=release
# 通用
ENV TZ=Asia/Shanghai
ENV LANG=C.UTF-8
ENV LC_ALL=C.UTF-8
运行时覆盖 ENV
FROM python:3.12-slim
ENV APP_ENV=development
ENV LOG_LEVEL=debug
CMD ["python", "app.py"]
# 运行时覆盖环境变量
docker run -e APP_ENV=production -e LOG_LEVEL=info myapp
# 使用 env 文件
docker run --env-file .env myapp
# 查看容器的环境变量
docker inspect --format='{{.Config.Env}}' myapp
5.3 ARG 指令详解
基本语法
# 定义带默认值的构建参数
ARG NODE_VERSION=20
ARG APP_VERSION=1.0.0
# 不带默认值(必须通过 --build-arg 传入)
ARG REGISTRY_URL
# 使用构建参数
FROM node:${NODE_VERSION}-alpine
LABEL version="${APP_VERSION}"
通过 –build-arg 传入
# 传入构建参数
docker build \
--build-arg NODE_VERSION=22 \
--build-arg APP_VERSION=2.0.0 \
--build-arg REGISTRY_URL=registry.example.com \
-t myapp .
# 如果 ARG 有默认值,不传入则使用默认值
docker build -t myapp . # NODE_VERSION=20, APP_VERSION=1.0.0
ARG 的作用域
ARG 的作用域从声明处开始,到遇到下一个 FROM 时结束:
# 全局 ARG(在第一个 FROM 之前)
ARG VERSION=20
FROM node:${VERSION}-alpine AS builder
# 此处 VERSION 不可用(作用域已被 FROM 截断)
ARG BUILD_ENV=production
RUN echo "Building for ${BUILD_ENV}"
FROM node:${VERSION}-alpine AS production
# 此处 VERSION 也不可用
# 需要重新声明
ARG VERSION
ARG BUILD_ENV
RUN echo "Version ${VERSION}, env ${BUILD_ENV}"
在 FROM 中使用 ARG
# ARG 在 FROM 之前声明,作用于所有 FROM 指令
ARG BASE_IMAGE=ubuntu
ARG BASE_TAG=22.04
FROM ${BASE_IMAGE}:${BASE_TAG} AS base
RUN echo "Base: ${BASE_IMAGE}:${BASE_TAG}"
FROM ${BASE_IMAGE}:${BASE_TAG}-slim AS slim
RUN echo "Slim: ${BASE_IMAGE}:${BASE_TAG}-slim"
5.4 ENV vs ARG 深度对比
生命周期对比
构建阶段(docker build):
┌───────────────────────────────────────────┐
│ ARG ──▶ 可用于 FROM, RUN, COPY 等 │
│ ENV ──▶ 可用于 FROM, RUN, COPY 等 │
└───────────────────────────────────────────┘
运行阶段(docker run):
┌───────────────────────────────────────────┐
│ ARG ──▶ ❌ 不可用(未持久化到镜像) │
│ ENV ──▶ ✅ 可用(持久化到镜像) │
└───────────────────────────────────────────┘
结合使用 ARG 和 ENV
FROM python:3.12-slim
# 通过 ARG 接收构建参数,赋值给 ENV 持久化
ARG APP_VERSION=1.0.0
ENV APP_VERSION=${APP_VERSION}
ARG LOG_LEVEL=info
ENV LOG_LEVEL=${LOG_LEVEL}
# 运行时可以通过 docker run -e LOG_LEVEL=debug 覆盖
CMD ["python", "app.py"]
缓存影响
# ❌ ARG 变化会导致后续所有层缓存失效
ARG DEPS_VERSION=1.0
COPY requirements-${DEPS_VERSION}.txt .
RUN pip install -r requirements-${DEPS_VERSION}.txt
COPY . .
# ✅ 将变化频率高的 ARG 放在后面
FROM python:3.12-slim
COPY . /app
WORKDIR /app
ARG APP_VERSION=1.0.0
RUN echo "Building version ${APP_VERSION}"
5.5 秘密变量处理
❌ 不安全的做法
# 密码会永久保存在镜像层中!
ENV DB_PASSWORD=supersecret123
ARG API_KEY=sk-1234567890
# 即使后续删除 ENV,密码仍在历史层中
RUN unset DB_PASSWORD
# 任何人都可以通过 docker history 看到
docker history myimage --no-trunc
✅ 安全的做法:BuildKit Secrets
# syntax=docker/dockerfile:1
FROM python:3.12-slim
# 挂载 secret 文件(不会保存到镜像层)
RUN --mount=type=secret,id=db_password \
DB_PASSWORD=$(cat /run/secrets/db_password) && \
python setup_db.py --password "$DB_PASSWORD"
# 方法一:从文件传入
echo "supersecret123" > .db_password
docker build --secret id=db_password,src=.db_password -t myapp .
rm .db_password
# 方法二:从环境变量传入
DB_PASSWORD=supersecret123 docker build \
--secret id=db_password,env=DB_PASSWORD -t myapp .
✅ 运行时传入秘密
FROM python:3.12-slim
COPY . /app
WORKDIR /app
# 不在 Dockerfile 中硬编码任何秘密
CMD ["python", "app.py"]
# 运行时传入环境变量
docker run -e DB_PASSWORD=supersecret123 myapp
# 运行时挂载 secret 文件
docker run -v /path/to/secrets:/run/secrets:ro myapp
# 使用 Docker Swarm secrets
docker secret create db_password .db_password
docker service create --secret db_password myapp
秘密处理方案对比
| 方案 | 安全性 | 便捷性 | 适用阶段 |
|---|---|---|---|
| ENV 硬编码 | ❌ 泄露风险 | ✅ | ❌ 永远不要 |
| ARG 硬编码 | ❌ 泄露风险 | ✅ | ❌ 永远不要 |
| BuildKit –mount=type=secret | ✅ | ✅ | 构建阶段 |
| 运行时环境变量 | ✅ | ✅ | 运行阶段 |
| 运行时挂载文件 | ✅ | ⚠️ | 运行阶段 |
| Docker Swarm Secrets | ✅ | ⚠️ | 生产运行 |
| Kubernetes Secrets | ✅ | ⚠️ | K8s 运行 |
5.6 业务场景:多环境构建
FROM node:20-alpine
# 构建参数:用于多环境构建
ARG APP_ENV=development
ARG API_URL
ARG FEATURE_FLAGS=""
# 环境变量:运行时默认值
ENV APP_ENV=${APP_ENV}
ENV NODE_ENV=production
WORKDIR /app
COPY package*.json ./
RUN npm ci --production
COPY . .
# 根据环境构建(React 示例)
RUN if [ "$APP_ENV" = "production" ]; then \
npm run build; \
else \
npm run build:staging; \
fi
CMD ["node", "server.js"]
# 开发环境
docker build --build-arg APP_ENV=development \
--build-arg API_URL=http://localhost:8080 \
-t myapp:dev .
# 生产环境
docker build --build-arg APP_ENV=production \
--build-arg API_URL=https://api.example.com \
-t myapp:prod .
5.7 特殊环境变量
Docker 预定义了一些特殊变量:
| 变量 | 说明 |
|---|---|
PATH | 可执行文件搜索路径 |
HOME | 当前用户主目录 |
HOSTNAME | 容器主机名 |
TERM | 终端类型 |
# 修改 PATH
ENV PATH="/opt/app/bin:${PATH}"
# 设置时区
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
5.8 常见错误与排查
| 错误 | 原因 | 解决方案 |
|---|---|---|
| ARG 值为空 | 未传入且无默认值 | 添加默认值或构建时传入 |
| ENV 在 exec 形式 CMD 中未替换 | exec 形式不经过 shell | 使用 shell 形式或 sh -c |
| 敏感信息出现在 docker history | 使用 ENV/ARG 存储秘密 | 使用 BuildKit secrets |
| ARG 跨 FROM 不可用 | 作用域被 FROM 截断 | 在每个阶段重新声明 ARG |
| 缓存意外失效 | ARG 值变化 | 将 ARG 放在文件变化之后 |
5.9 扩展阅读
上一章:04 - RUN 指令 下一章:06 - CMD 与 ENTRYPOINT — 默认命令、入口点、信号处理与 SHELL 指令。