强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

Dockerfile 写作精讲 / 05 - ENV 与 ARG

05 - ENV 与 ARG:环境变量与构建参数

5.1 指令概述

ENVARG 都用于定义变量,但它们的作用域和用途有本质区别。

特性ENVARG
作用阶段构建 + 运行时仅构建阶段
持久化到镜像
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"]

注意CMDENTRYPOINT 使用 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 指令。