> For the complete documentation index, see [llms.txt](https://yeasy.gitbook.io/docker_practice/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://yeasy.gitbook.io/docker_practice/di-er-bu-fen-jin-jie-pian/07_dockerfile/7.6_env.md).

# 7.6 ENV 设置环境变量

## 7.6.1 基本语法

```docker
## 格式一：单个变量

ENV <key>=<value>

## 格式二：多个变量

ENV <key1>=<value1> <key2>=<value2> ...
```

> ⚠️ 用空格分隔的旧写法 `ENV <key> <value>`（`ARG` 同理）虽然仍能构建，但已被官方弃用。BuildKit 的 [LegacyKeyValueFormat](https://docs.docker.com/reference/build-checks/legacy-key-value-format/) 构建检查会报 `"ENV key=value" should be used instead of legacy "ENV key value" format`，`docker buildx build --check`（见 [10.2](/docker_practice/di-er-bu-fen-jin-jie-pian/10_buildx/10.2_buildx.md)）会直接把它标出来。一律用等号。

***

## 7.6.2 基本用法

### 设置单个变量

```docker
ENV NODE_VERSION=20
ENV APP_ENV=production
```

### 设置多个变量

```docker
ENV NODE_VERSION=20 \
    APP_ENV=production \
    APP_NAME="My Application"
```

> 💡 包含空格的值用双引号括起来。

***

## 7.6.3 环境变量的作用

### 1. 后续指令中使用

```docker
ENV NODE_VERSION=20.10.0

## 在 RUN 中使用

RUN curl -fsSL https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-linux-x64.tar.xz \
    | tar -xJ -C /usr/local --strip-components=1

## 在 WORKDIR 中使用

ENV APP_HOME=/app
WORKDIR $APP_HOME

## 在 COPY 中使用

COPY . $APP_HOME
```

### 2. 容器运行时使用

```docker
ENV DATABASE_URL=postgres://localhost/mydb
```

应用代码中可以读取：

```python
import os
db_url = os.environ.get('DATABASE_URL')
```

```javascript
const dbUrl = process.env.DATABASE_URL;
```

***

## 7.6.4 支持环境变量的指令

以下指令可以使用 `$变量名` 或 `${变量名}` 格式：

| 指令           | 示例                               |
| ------------ | -------------------------------- |
| `RUN`        | `RUN echo $VERSION`              |
| `CMD`        | `CMD ["sh", "-c", "echo $HOME"]` |
| `ENTRYPOINT` | 同上                               |
| `COPY`       | `COPY . $APP_HOME`               |
| `ADD`        | `ADD app.tar.gz $APP_HOME`       |
| `WORKDIR`    | `WORKDIR $APP_HOME`              |
| `EXPOSE`     | `EXPOSE $PORT`                   |
| `VOLUME`     | `VOLUME $DATA_DIR`               |
| `USER`       | `USER $USERNAME`                 |
| `LABEL`      | `LABEL version=$VERSION`         |
| `FROM`       | `FROM node:$NODE_VERSION`        |

> **注意**：`FROM` 是上表中的特例——它只能引用在第一条 `FROM` 之前用 `ARG` 声明的变量，**无法**使用 `ENV` 定义的环境变量（`ENV` 要进入构建阶段后才生效）。其余指令才能引用 `ENV` 环境变量。

***

## 7.6.5 运行时覆盖

使用 `-e` 或 `--env` 覆盖 Dockerfile 中定义的环境变量：

```bash
## 覆盖单个变量

$ docker run -e APP_ENV=development myimage

## 覆盖多个变量

$ docker run -e APP_ENV=development -e DEBUG=true myimage

## 从环境变量文件读取

$ docker run --env-file .env myimage
```

### .env 文件格式

```bash
## .env

APP_ENV=development
DEBUG=true
DATABASE_URL=postgres://localhost/mydb
```

***

## 7.6.6 ENV vs ARG

| 特性       | ENV             | ARG                        |
| -------- | --------------- | -------------------------- |
| **生效时间** | 构建时 + 运行时       | 仅构建时                       |
| **持久性**  | 写入镜像，运行时可用      | 构建后消失                      |
| **覆盖方式** | `docker run -e` | `docker build --build-arg` |
| **适用场景** | 应用配置            | 构建参数（如版本号）                 |

### 组合使用

```docker
## ARG 接收构建时参数

ARG NODE_VERSION=20

## ENV 保存到运行时

ENV NODE_VERSION=$NODE_VERSION

## 后续指令使用

RUN curl -fsSL https://nodejs.org/dist/v${NODE_VERSION}/...
```

```bash
## 构建时指定版本

$ docker build --build-arg NODE_VERSION=18 -t myapp .
```

***

## 7.6.7 最佳实践

### 1. 统一管理版本号

```docker
## ✅ 好：版本集中管理

ENV NGINX_VERSION=1.30 \
    NODE_VERSION=22 \
    PYTHON_VERSION=3.12

RUN apt-get install nginx=${NGINX_VERSION}

## ❌ 差：版本分散在各处

RUN apt-get install nginx=1.30
```

### 2. 不要存储敏感信息

```docker
## ❌ 错误：密码写入镜像

ENV DB_PASSWORD=secret123

## ⚠️ 好一些但仍会泄漏：-e 会进入 shell history、宿主机进程 argv（同机任何用户 ps 可见），
## 并永久固化在 docker inspect 的 Config.Env 里

## docker run -e DB_PASSWORD=xxx myimage

## ✅ 正确：用文件传入，不进命令行

## docker run --env-file ./db.env myimage        # 文件权限设为 600
## 或使用 Compose secrets / *_FILE 约定，见 11.8 节

...
```

### 3. 为应用提供合理默认值

```docker
ENV APP_ENV=production \
    APP_PORT=8080 \
    LOG_LEVEL=info
```

### 4. 使用有意义的变量名

```docker
## ✅ 好：清晰的命名

ENV REDIS_HOST=localhost \
    REDIS_PORT=6379

## ❌ 差：模糊的命名

ENV HOST=localhost \
    PORT=6379
```

***

## 7.6.8 常见问题

### Q：环境变量在 CMD 中不展开

exec 格式不会自动执行 shell 展开，因此命令参数里的 `$PORT` 会按字面值传给进程；但环境变量本身仍会注入进程环境，应用可以通过语言运行时读取。

```docker
## ❌ 不会展开 $PORT

CMD ["python", "app.py", "--port", "$PORT"]

## ✅ 使用 shell 格式或显式调用 sh

CMD ["sh", "-c", "python app.py --port $PORT"]
```

### Q：如何查看容器的环境变量

```bash
$ docker inspect mycontainer --format '{{json .Config.Env}}'
$ docker exec mycontainer env
```

### Q：多行 ENV 还是多个 ENV

```docker
## ✅ 推荐：减少层数

ENV VAR1=value1 \
    VAR2=value2 \
    VAR3=value3

## ⚠️ 多个 ENV 会多出几条构建历史记录（元数据指令，不增加文件系统层）

ENV VAR1=value1
ENV VAR2=value2
ENV VAR3=value3
```

***
