---
url: /zh/build/env.md
description: |
  介绍云原生构建中环境变量的声明、导入、导出和使用方法， 涵盖 Pipeline 和 Job 级别的配置。
---
`云原生构建` 支持声明构建环境和定义构建过程中使用的环境变量。

`云原生构建` 内置了一些[默认环境变量](./build-in-env.md)，方便直接使用。

## 功能概览

环境变量支持以下功能：

| 功能                | 说明                    |
| ----------------- | --------------------- |
| [声明环境变量](#声明环境变量) | 通过 `env` 声明环境变量       |
| [导入环境变量](#导入环境变量) | 通过 `imports` 导入密钥仓库文件 |
| [导出环境变量](#导出环境变量) | 将任务结果导出为环境变量          |
| [使用环境变量](#使用环境变量) | 在脚本和配置中引用环境变量         |

## 声明环境变量

通过 `env` 声明环境变量：

* 在 `Pipeline` 中声明的环境变量对整个流水线有效。
* 在 `Job` 中声明的环境变量仅对当前任务有效。

以下示例中，`docker.image` 声明了构建环境为 `node:22`，
Pipeline 级别的 `env` 声明了两个流水线环境变量，
Job 级别声明了一个仅对当前任务有效的环境变量：

```yaml title=".cnb.yml"
main:
  push:
    - docker:
        image: node:22
      env:
        PIPELINE_ENV_1: pipeline environment variable 1
        PIPELINE_ENV_2: pipeline environment variable 2
      stages:
        - name: 输出构建环境信息
          script: node -v
        - name: 输出流水线环境变量
          script:
            - echo $PIPELINE_ENV_1
            - echo $PIPELINE_ENV_2
        - name: 输出任务环境变量
          env:
            JOB_ENV: job environment variable
          script: echo $JOB_ENV
```

## 导入环境变量

通过 `imports` 导入密钥仓库文件，将敏感信息注入为环境变量，供后续任务使用。

### 优先级规则

当 `env` 和 `imports` 的 `key` 冲突时，优先使用 `env`。

### 示例

以下示例导入密钥仓库文件 `envs.yml`，
其中的 `TEST_DOCKER_DOMAIN`、`TEST_DOCKER_USER`、`TEST_DOCKER_PWD`
变量可供后续任务使用：

```yaml title=".cnb.yml"
main:
  push:
    - services:
        - docker
      imports: https://cnbprod.alpha.qq.com/<your-repo-slug>/-/blob/main/xxx/envs.yml
      stages:
        - name: docker info
          script: docker info
        - name: docker login
          script: docker login $TEST_DOCKER_DOMAIN -u $TEST_DOCKER_USER -p $TEST_DOCKER_PWD
```

**envs.yml 内容示例**：

```yaml
# docker 镜像源域名
TEST_DOCKER_DOMAIN: registry.example.com
# docker 用户名
TEST_DOCKER_USER: your_docker_username
# docker 密码
TEST_DOCKER_PWD: your_docker_password
```

## 导出环境变量

`Job` 执行结束后，会有 `result` 对象，
可通过 [exports](./grammar.md#job-exports) 将其属性导出为环境变量，
生命周期为当前 `Pipeline`。

### 语法格式

```yaml
exports:
  from-key: to-key
```

**参数说明**：

| 参数       | 说明                    |
| -------- | --------------------- |
| from-key | `Job result` 中要导出的属性名 |
| to-key   | 映射到环境变量中的变量名          |

* `from-key` 支持环境变量，支持深层取值（参考 `lodash.get`）。

### result 设置方式

以下三种方式可设置 result：

| 方式           | 说明          |
| ------------ | ----------- |
| 脚本任务执行结果     | 脚本执行完成后的输出  |
| 解析输出中的自定义变量  | 通过特殊格式输出变量  |
| 内置任务的 result | 内置任务返回的结果对象 |

### 脚本任务执行结果

`script` 脚本任务执行完成后，`Job result` 包含以下属性：

| 属性     | 说明                    |
| ------ | --------------------- |
| code   | 返回码                   |
| stdout | 标准输出                  |
| stderr | 标准错误                  |
| info   | stdout 和 stderr 按时序混合 |

::: tip 输出技巧
可使用 `printf "%s" "hello\nworld"` 来输出变量，以消除标准输出流最后的换行符，
同时保留 `\n` 等转义字符。
:::

**示例**：

```yaml title=".cnb.yml"
main:
  push:
    - stages:
        - name: set env
          script: echo -n $(date "+%Y-%m-%d %H:%M")
          exports:
            code: CUSTOM_ENV_DATE_CODE
            info: CUSTOM_ENV_DATE_INFO
        - name: echo env
          script:
            - echo $CUSTOM_ENV_DATE_CODE
            - echo $CUSTOM_ENV_DATE_INFO
```

**包含判断逻辑时的属性**：

当包含 `if`、`ifModify`、`ifNewBranch` 等条件判断时，可额外获取以下属性：

| 属性   | 说明                   |
| ---- | -------------------- |
| skip | 任务被跳过时返回跳过原因，否则为空字符串 |

仅当条件判断（`if`、`ifModify`、`ifNewBranch`）导致任务被跳过时，`skip` 属性才会出现。

```yaml
- name: use if
  if: exit -1
  exports:
    skip: REASON
- name: tell last
  # $REASON 的值为 if 这个字符串
  script: echo $REASON
```

### 解析输出中的自定义变量

CI 从标准输出流中按行识别 `##[set-output key=value]` 格式的内容，自动解析到 `result` 对象中。

#### 变量值编码

若变量值包含换行符 `\n`，可用 `base64` 或 `escape` 编码变量值：

| 编码方式   | 说明                            |
| ------ | ----------------------------- |
| base64 | 值以 `base64,` 开始，系统做 base64 解码 |
| escape | 系统对变量值做 unescape 解码           |

**方式一 — Node.js**（需 `test.js` 文件，同时输出 base64 和 escape）：

```yaml title=".cnb.yml"
main:
  push:
    - docker:
        image: node:20-alpine
      stages:
        - name: set output env
          script: node test.js
          exports:
            redline_msg_base64: BASE64_KEY
            redline_msg_escape: ESCAPE_KEY
        - name: echo env
          script:
            - echo "BASE64_KEY $BASE64_KEY"
            - echo "ESCAPE_KEY $ESCAPE_KEY"
```

**方式二 — Shell echo**（纯 Shell，无需脚本文件，仅输出 base64）：

```yaml title=".cnb.yml"
main:
  push:
    - stages:
        - name: set output env
          script: echo "##[set-output redline_msg_base64=base64,$(echo -e "测试字符串\ntest string" | base64 -w 0)]"
          exports:
            redline_msg_base64: BASE64_KEY
        - name: echo env
          script:
            - echo "BASE64_KEY $BASE64_KEY"
```

**Node.js 脚本**（`test.js`，用于上述方式一）：

```javascript title="test.js"
const value = '测试字符串\ntest string';
console.log(`##[set-output redline_msg_base64=base64,${Buffer.from(value, 'utf-8').toString('base64')}]`);
console.log(`##[set-output redline_msg_escape=${escape(value)}]`);
```

::: warning 注意
在 Unix-like 系统中，base64 命令默认会在每 76 个字符后添加一个换行符，
可使用 `-w 0` 选项来禁用换行，避免 CI 未能按行解析出变量。
:::

**不包含 `\n` 的变量值**可直接输出：

```bash
echo "##[set-output redline_msg=some value]"
```

::: tip 限制说明

* 受限于系统环境变量值长度限制，过大的变量值无效
* CI 会忽略大于等于 `100KB` 的变量值，可写入文件中自行解析
* 对于敏感信息，建议使用 [read-file](./internal-steps/cnb/read-file.md) 内置任务
  :::

### 输出敏感变量（set-secret）

CI 从标准输出流中按行识别 `##[set-secret key=value]` 格式的内容，解析到 `result` 对象中，
用法与 [set-output](#解析输出中的自定义变量) 完全一致：通过 `exports` 导出为环境变量后，
对后续整个 `Pipeline` 有效。

与 `set-output` 的唯一区别是：`set-secret` 的值会被登记为**敏感值**，
在构建日志中自动打码（内置 token 如 `$CNB_TOKEN` 也同样被打码），
适合输出密码、令牌等敏感信息。

```yaml title=".cnb.yml"
main:
  push:
    - stages:
        - name: set secret env
          script: echo "##[set-secret redline_secret=base64,$(echo -n "secret-value" | base64 -w 0)]"
          exports:
            redline_secret: MY_TOKEN
        - name: use secret
          # 脚本中 $MY_TOKEN 为真实值；日志里打印出来会打码显示（如 s************e）
          script: echo "$MY_TOKEN"
```

变量值同样支持 `base64` / `escape` 编码，`100KB` 长度限制、变量名格式等规则与 `set-output` 一致。
含换行等特殊字符的多行值（如 SSH/TLS 私钥），需先用 `base64` / `escape` 编码后再放入单行声明。

::: warning 注意

* 该特性依赖构建机（runner）支持：旧版本 runner 会把 `##[set-secret key=value]` 当作普通输出打印，
  敏感值将以**明文出现在日志中**且无任何提示。使用自托管（self-hosted）构建机时请先升级 runner。
* 掩码仅影响**日志展示**，不影响脚本中变量的真实取值。
* 与 `set-output` 一致，每行输出中只有第一个 `##[...]` 声明会被解析。
* 若在日志中对 secret 做二次编码或转换后输出（如 `base64`、`tr` 转码），转换后的内容将无法被掩码。
  :::

### 内置任务中导出环境变量

一些内置任务有输出结果，可通过 `exports` 导出为环境变量。

```yaml title=".cnb.yml"
main:
  push:
    - stages:
        - name: xxxx
          type: xxx:xxx
          options:
            product: public
            name: cnb
            dist: release/
          exports:
            version: CUSTOM_ENV_VERSION
            url: CUSTOM_ENV_URL
            nextRelease.gitTag: CUSTOM_ENV_GIT_TAG
        - name: echo env
          script:
            - echo $CUSTOM_ENV_VERSION
            - echo $CUSTOM_ENV_URL
```

`result` 内容参考各内置任务文档。

### 子流水线传递变量给父流水线

通过 [cnb:apply](./internal-steps/cnb/apply.md)（同仓库）或
[cnb:trigger](./internal-steps/cnb/trigger.md)（跨仓库）触发的流水线，
下称「子流水线」。
在 `sync: true` 同步模式下，其 `result` 会带回 `lastJobExports`——
即子流水线**最后一个 Job 通过 `exports` 导出的全部环境变量**。
父流水线借此即可拿到子流水线的产出。

同步模式下 `result` 的主要属性：

| 属性             | 说明                     |
| -------------- | ---------------------- |
| sn             | 子流水线构建号                |
| buildLogUrl    | 子流水线构建日志链接             |
| buildSuccess   | 子流水线是否构建成功（仅同步模式存在）    |
| lastJobExports | 子流水线最后一个 Job 导出的环境变量对象 |

::: warning 注意
只有 `sync: true` 才会等待子流水线执行完成并带回 `lastJobExports`；异步模式下父流水线不等待，也拿不到这些结果。
:::

**示例**：子流水线输出并导出一个变量，父流水线同步触发后取回使用。

```yaml title=".cnb.yml"
main:
  api_trigger_test:
    - stages:
        - name: 子流水线输出变量
          script: echo "##[set-output sub_result=base64,$(echo -e "测试字符串\ntest string" | base64 -w 0)]"
          exports:
            sub_result: SUB_RESULT

  push:
    - stages:
        - name: 触发子流水线并等待
          type: cnb:apply
          options:
            event: api_trigger_test
            sync: true
          exports:
            lastJobExports: SUB_EXPORTS
            lastJobExports.SUB_RESULT: SUB_RESULT
            sn: SUB_SN
        - name: 使用子流水线传回的变量
          script: |
            echo "SUB_EXPORTS: $SUB_EXPORTS"
            echo "SUB_SN: $SUB_SN"
            echo "SUB_RESULT: $SUB_RESULT"
```

跨仓库场景把 `cnb:apply` 换成
[cnb:trigger](./internal-steps/cnb/trigger.md) 并指定目标仓库 `slug` 即可，
取回变量的方式相同。

### 增删改查环境变量

不支持直接修改；设为空字符串或 `null` 即可删除。

```yaml title=".cnb.yml"
main:
  push:
    - env:
        CUSTOM_ENV_DATE_INFO: default
        CUSTOM_ENV_FOR_DELETE: default
      stages:
        - name: set env
          script: echo -n $(date "+%Y-%m-%d %H:%M")
          exports:
            # 新增
            code: CUSTOM_ENV_DATE_CODE
            # 修改
            info: CUSTOM_ENV_DATE_INFO
            # 删除：将值设为 null 或空字符串
            CUSTOM_ENV_FOR_DELETE: null
        - name: echo env
          script:
            - echo $CUSTOM_ENV_DATE_CODE
            - echo $CUSTOM_ENV_DATE_INFO
            - echo $CUSTOM_ENV_FOR_DELETE
```

## 使用环境变量

### 在脚本任务中使用

当任务执行时，流水线声明的环境变量会作为任务的环境变量传入。以下示例中 `CNB_BRANCH` 为内置环境变量：

```yaml title=".cnb.yml"
main:
  push:
    - stages:
        - name: test internal env
          script: echo $CNB_BRANCH
        - name: test self defined env
          env:
            cat_name: tomcat
          script: echo $cat_name
```

### 变量替换

配置文件中的一些属性值会进行变量替换。

**替换规则**：如有环境变量 `env_name=env_value`，
则属性值中的 `$env_name` 会被替换为 `env_value`；
若 `env_name` 无值，则替换为空字符串。

#### 支持变量替换的属性

| 属性                      | 说明                                        |
| ----------------------- | ----------------------------------------- |
| 内置任务 options            | `options` 和 `optionsFrom`                 |
| 插件任务 settings           | `settings` 和 `settingsFrom`               |
| env                     | 可引用上层 `env` 中的变量                          |
| imports                 | imports 及声明文件中的属性值                        |
| pipeline.runner.tags    | 构建节点标签                                    |
| pipeline.docker         | `volumes`、`image` 和 `build`               |
| stage.image 和 job.image | 环境镜像配置                                    |
| ifModify                | 文件变更条件                                    |
| name                    | `pipeline.name`、`stage.name` 和 `job.name` |
| lock.key                | 锁的键名                                      |
| allowFailure            | 允许失败配置                                    |

#### 内置任务示例

```yaml
# options.yml
name: Nightly
```

```yaml title=".cnb.yml"
main:
  push:
    - env:
        address: options.yml
        description: publish for xx task
      stages:
        - name: git release
          type: git:release
          optionsFrom: $address
          options:
            description: $description
```

**options 的最终内容**：

```yaml
name: Nightly
description: publish for xx task
```

#### 插件任务示例

```yaml
# settings.yml
robot: https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=xxx
```

```yaml title=".cnb.yml"
main:
  push:
    - env:
        address: settings.yml
        message: pr check
      stages:
        - name: notify
          image: tencentcom/wecom-message
          settingsFrom: $address
          settings:
            content: $message
```

**settings 的最终内容**：

```yaml
robot: https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=xxx
message: pr check
```

#### Dockerfile 中的 settingsFrom

写在 Dockerfile 中的 `LABEL` 指定的 `settingsFrom` 同样可以进行变量替换：

```dockerfile
FROM node:20

LABEL cnb.cool/settings-from="$address"
```

#### env 示例

env 下声明的属性值可以引用上层 env 中的变量进行替换。以下示例中 `$cat_name` 会被替换为上层的 `tomcat`：

```yaml title=".cnb.yml"
main:
  push:
    - env:
        cat_name: tomcat
      stages:
        - name: echo env
          env:
            name: "cat $cat_name"
          script: echo $name
```

#### imports 示例

imports 的属性值以及所声明的文件中的属性值均会进行变量替换。
若 imports 为数组，数组前面文件中声明的变量对数组后面元素有效。

以下示例中 `env1.yml` 声明了 `address: env2.yml`，`$address` 会被替换为 `env2.yml`，进而导入 `env2.yml` 中的变量：

```yaml
# env1.yml
address: env2.yml
platform: amd64
```

```yaml
# env2.yml
action: build for $platform
```

```yaml title=".cnb.yml"
main:
  push:
    - imports:
        - env1.yml
        - $address
      stages:
        - name: echo action
          script: echo $action
```

#### pipeline.runner.tags 示例

通过流水线名称 `CNB_PIPELINE_NAME` 注入架构名，可为不同架构声明各自独立的流水线：

```yaml title=".cnb.yml"
main:
  push:
    # amd64 会成为内置环境变量 CNB_PIPELINE_NAME 的值
    amd64:
      runner:
        tags: cnb:arch:amd64
      services:
        - docker
      stages:
        - name: docker build
          script: echo "docker build for $CNB_PIPELINE_NAME"
    # arm64:v8 会成为内置环境变量 CNB_PIPELINE_NAME 的值
    "arm64:v8":
      runner:
        tags: cnb:arch:arm64:v8
      services:
        - docker
      stages:
        - name: docker build
          script: echo "docker build for $CNB_PIPELINE_NAME"
```

#### pipeline.docker 示例

```yaml title=".cnb.yml"
main:
  push:
    - docker:
        image: node:22-alpine
        volumes:
          - /data
      stages:
        - name: echo
          script: echo "building with docker"
```

#### ifModify 示例

通过流水线名称 `CNB_PIPELINE_NAME` 注入模块名，仅在对应模块代码变更时才编译该模块：

```yaml title=".cnb.yml"
# 不同模块下代码有变更才进行对应模块的编译
main:
  push:
    module-a:
      ifModify: module-a/*
      stages:
        - name: build module-a
          script: echo "build $CNB_PIPELINE_NAME"
    module-b:
      ifModify: module-b/*
      stages:
        - name: build module-b
          script: echo "build $CNB_PIPELINE_NAME"
```

#### name 示例

pipeline.name、stage.name 和 job.name 的属性值会进行变量替换：

```yaml title=".cnb.yml"
main:
  push:
    - name: build in $CNB_REPO_SLUG
      env:
        platform: amd64
      imports:
        - env1.yml
        - env2.yml
      stages:
        - name: stage_$SOME_ENV
          script: echo "hello world"
```

#### lock.key 示例

通过 `imports` 引入密钥文件中的锁键名，保证同一时刻只有一条流水线占用该锁：

```yaml
# env.yml
build_key: build key
```

```yaml title=".cnb.yml"
main:
  push:
    # 以下两条流水线，一条占用了锁成功执行，另一条未占到锁执行失败
    - imports: env.yml
      lock:
        key: $build_key
      stages:
        - name: echo
          script: echo "hello world"
    - imports: env.yml
      lock:
        key: $build_key
      stages:
        - name: echo
          script: echo "hello world"
```

#### allowFailure 示例

```yaml title=".cnb.yml"
main:
  push:
    - env:
        allow_fail: true
      stages:
        - name: echo
          allowFailure: $allow_fail
          script: echo 1
```

### 阻止变量替换

如果不希望 `$env_name` 被替换，可以通过 `\$` 阻止替换：

```yaml title=".cnb.yml"
main:
  push:
    - stages:
        - name: git release
          type: git:release
          options:
            name: Development
            description: some code update \$description
```

## 限制说明

环境变量有以下限制：

| 限制项   | 说明                   |
| ----- | -------------------- |
| 变量名格式 | 仅支持字母、数字和下划线，不能以数字开头 |
| 变量值长度 | 不能超过 `100KiB`        |

::: warning 注意
不符合上述规则的变量会被忽略。
:::

## 代理环境变量自动同步

当流水线在 Docker 容器中执行时，以下代理相关环境变量会自动从 Runner 宿主机同步到容器内，
无需在 `.cnb.yml` 中手动声明：

| 变量名           | 说明             |
| ------------- | -------------- |
| `HTTP_PROXY`  | HTTP 代理地址      |
| `HTTPS_PROXY` | HTTPS 代理地址     |
| `NO_PROXY`    | 不走代理的地址列表      |
| `http_proxy`  | HTTP 代理地址（小写）  |
| `https_proxy` | HTTPS 代理地址（小写） |
| `no_proxy`    | 不走代理的地址列表（小写）  |

::: tip 取值规则
未在 `env` 中声明时，这些代理变量会自动继承 Runner 宿主机的配置，无需手动设置；
如在 `env` 中声明，则以用户声明的值为准。
:::
