---
url: /en/build/env.md
description: >
  Covers how to declare, import, export, and use environment variables in Cloud
  Native Build pipelines, including pipeline-level and job-level variable
  scoping.
---
`Cloud Native Build` supports declaring the build environment and defining
environment variables used during the build process.

`Cloud Native Build` comes with some [default environment variables](./build-in-env.md) for direct use.

## Feature Overview

Environment variables support the following features:

| Feature                                                           | Description                                           |
| ----------------------------------------------------------------- | ----------------------------------------------------- |
| [Declare Environment Variables](#declaring-environment-variables) | Declare environment variables via `env`               |
| [Import Environment Variables](#importing-environment-variables)  | Import secret vault files via `imports`               |
| [Export Environment Variables](#exporting-environment-variables)  | Export task results as environment variables          |
| [Use Environment Variables](#using-environment-variables)         | Reference environment variables in scripts and config |

## Declaring Environment Variables

Declare environment variables via `env`:

* Environment variables declared in `Pipeline` are effective for the entire pipeline.
* Environment variables declared in `Job` are only effective for the current task.

In the example below, `docker.image` declares the build environment as `node:22`,
Pipeline-level `env` declares two environment variables, and Job-level declares
a variable effective only for that task:

```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: Output build environment information
          script: node -v
        - name: Output pipeline environment variables
          script:
            - echo $PIPELINE_ENV_1
            - echo $PIPELINE_ENV_2
        - name: Output job environment variables
          env:
            JOB_ENV: job environment variable
          script: echo $JOB_ENV
```

## Importing Environment Variables

Use `imports` to import a secrets file, injecting sensitive information as
environment variables for use by subsequent tasks.

### Priority Rules

When there's a conflict between `env` and `imports` keys, `env` takes precedence.

### Example

The example below imports the secrets file `envs.yml`,
where `TEST_DOCKER_DOMAIN`, `TEST_DOCKER_USER`, `TEST_DOCKER_PWD`
are available for subsequent tasks:

```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 content example**:

```yaml
# Docker registry domain
TEST_DOCKER_DOMAIN: registry.example.com
# Docker username
TEST_DOCKER_USER: your_docker_username
# Docker password
TEST_DOCKER_PWD: your_docker_password
```

## Exporting Environment Variables

After a `Job` completes, there is a `result` object. You can use [exports](./grammar.md#job-exports)
to export its properties as environment variables, with a lifecycle of the current `Pipeline`.

### Syntax Format

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

**Parameter Description**:

| Parameter | Description                                      |
| --------- | ------------------------------------------------ |
| from-key  | Property name from `Job result` to export        |
| to-key    | Variable name to map to in environment variables |

* `from-key` supports environment variables and deep property access (similar to `lodash.get`).

### Result Setting Methods

The following three methods can set result:

| Method                             | Description                               |
| ---------------------------------- | ----------------------------------------- |
| Script task execution results      | Output after script execution completes   |
| Parse custom variables from output | Output variables through special format   |
| Built-in task results              | Result objects returned by built-in tasks |

### Script Task Execution Results

After a `script` task executes, the `Job result` contains the following properties:

| Property | Description                                     |
| -------- | ----------------------------------------------- |
| code     | Return code                                     |
| stdout   | Standard output                                 |
| stderr   | Standard error                                  |
| info     | Mixed stdout and stderr, in chronological order |

::: tip Output Tips
You can use `printf "%s" "hello\nworld"` to output variables,
which removes trailing newlines from standard output while preserving escape characters like `\n`.
:::

**Example**:

```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
```

**Properties with Conditional Logic**:

When conditional logic such as `if`, `ifModify`, `ifNewBranch` is present,
the following additional property is available:

| Property | Description                                            |
| -------- | ------------------------------------------------------ |
| skip     | Skip reason if task is skipped; empty string otherwise |

The `skip` property only appears when conditional logic (`if`, `ifModify`, `ifNewBranch`) evaluates to skip the task.

```yaml
- name: use if
  if: exit -1
  exports:
    skip: REASON
- name: tell last
  # $REASON value is the string "if"
  script: echo $REASON
```

### Parsing Custom Variables from Output

CI recognizes `##[set-output key=value]` formatted content line by line from the standard output stream and
automatically parses it into the `result` object.

#### Variable Value Encoding

If the variable value contains newline `\n`, you can encode the variable value with `base64` or `escape`:

| Encoding Method | Description                                    |
| --------------- | ---------------------------------------------- |
| base64          | Value starts with `base64,`, decoded as base64 |
| escape          | The system unescapes the variable value        |

**Approach 1 — Node.js** (requires `test.js`, outputs both base64 and 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"
```

**Approach 2 — Shell echo** (pure Shell, no script file needed, outputs base64 only):

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

**Node.js script** (`test.js`, used by Approach 1 above):

```javascript title="test.js"
const value = 'Test string\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 Note
In Unix-like systems, the base64 command adds a newline every 76 characters by default.
You can use the `-w 0` option to disable newlines to avoid CI failing to parse variables by line.
:::

**Variable values without `\n`** can be output directly:

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

::: tip Limitations

* Due to system environment variable length limits, excessively large variable values are invalid
* CI will ignore variable values >= `100KB`, you can write to files and parse them yourself
* For sensitive information, it's recommended to use the [read-file](./internal-steps/cnb/read-file.md) built-in task
  :::

### Outputting Sensitive Variables (set-secret)

CI recognizes `##[set-secret key=value]` formatted content line by line from the standard output stream and
parses it into the `result` object. It works exactly like [set-output](#parsing-custom-variables-from-output):
after being exported as an environment variable via `exports`, it is effective for the entire subsequent `Pipeline`.

The only difference from `set-output` is that the value of `set-secret` is registered as a **sensitive value**
and is automatically masked in the build log (built-in tokens such as `$CNB_TOKEN` are masked the same way),
making it suitable for outputting sensitive information such as passwords and tokens.

```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 holds the real value in the script; when printed to the log it is masked (e.g. s************e)
          script: echo "$MY_TOKEN"
```

Variable values also support `base64` / `escape` encoding, and the `100KB` length limit
and variable name format rules are the same as `set-output`.
Multi-line values containing newlines or other special characters (such as SSH/TLS private keys)
must be `base64` / `escape` encoded before being placed in the single-line declaration.

::: warning Note

* This feature requires runner support: older runners treat `##[set-secret key=value]` as normal output,
  and the sensitive value will appear **in the log in plaintext** without any warning.
  If you use self-hosted runners, upgrade the runner first.
* Masking only affects **log display**, not the real value of the variable in scripts.
* Consistent with `set-output`, only the first `##[...]` declaration on each output line is parsed.
* If a secret is re-encoded or transformed before being output in the log
  (e.g. `base64`, `tr`), the transformed content will not be masked.
  :::

### Exporting Environment Variables from Built-in Tasks

Some built-in tasks have output results that can be exported as environment variables via `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
```

Refer to each built-in task's documentation for `result` content.

### Passing Variables from a Sub-Pipeline to the Parent Pipeline

A pipeline triggered via [cnb:apply](./internal-steps/cnb/apply.md) (same repo) or
[cnb:trigger](./internal-steps/cnb/trigger.md) (cross repo)—referred to as a "sub-pipeline"—returns
`lastJobExports` in its `result` when running in `sync: true` mode. `lastJobExports` contains
**all environment variables exported via `exports` by the last Job of the sub-pipeline**,
allowing the parent pipeline to obtain the sub-pipeline's outputs.

Main `result` properties in sync mode:

| Property       | Description                                                   |
| -------------- | ------------------------------------------------------------- |
| sn             | Sub-pipeline build number                                     |
| buildLogUrl    | Sub-pipeline build log URL                                    |
| buildSuccess   | Whether the sub-pipeline succeeded (sync mode only)           |
| lastJobExports | Environment variables exported by the sub-pipeline's last Job |

::: warning Note
Only `sync: true` waits for the sub-pipeline to finish and returns `lastJobExports`.
In async mode, the parent pipeline does not wait and cannot get these results.
:::

**Example**: the sub-pipeline outputs and exports a variable,
and the parent pipeline retrieves it after a synchronous trigger.

```yaml title=".cnb.yml"
main:
  api_trigger_test:
    - stages:
        - name: sub-pipeline outputs a variable
          script: echo "##[set-output sub_result=base64,$(echo -e "test string\nsecond line" | base64 -w 0)]"
          exports:
            sub_result: SUB_RESULT

  push:
    - stages:
        - name: trigger sub-pipeline and wait
          type: cnb:apply
          options:
            event: api_trigger_test
            sync: true
          exports:
            lastJobExports: SUB_EXPORTS
            lastJobExports.SUB_RESULT: SUB_RESULT
            sn: SUB_SN
        - name: use variables returned by the sub-pipeline
          script: |
            echo "SUB_EXPORTS: $SUB_EXPORTS"
            echo "SUB_SN: $SUB_SN"
            echo "SUB_RESULT: $SUB_RESULT"
```

For cross-repo scenarios, replace `cnb:apply` with [cnb:trigger](./internal-steps/cnb/trigger.md)
and specify the target repo `slug`; variables are retrieved the same way.

### Managing Environment Variables

Existing environment variables cannot be modified directly. Setting to an empty string or `null` will delete them.

```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:
            # Add new
            code: CUSTOM_ENV_DATE_CODE
            # Modify
            info: CUSTOM_ENV_DATE_INFO
            # Delete: set the value to null or an empty string
            CUSTOM_ENV_FOR_DELETE: null
        - name: echo env
          script:
            - echo $CUSTOM_ENV_DATE_CODE
            - echo $CUSTOM_ENV_DATE_INFO
            - echo $CUSTOM_ENV_FOR_DELETE
```

## Using Environment Variables

### In Script Tasks

When tasks execute, pipeline-declared environment variables
are passed as task environment variables.
In the example below, `CNB_BRANCH` is a built-in environment variable:

```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
```

### Variable Substitution

Some property values in configuration files will undergo variable substitution.

**Substitution Rules**: If there's an environment variable `env_name=env_value`,
then `$env_name` in property values will be replaced with `env_value`. If `env_name` has no value,
it will be replaced with an empty string.

#### Properties Supporting Variable Substitution

| Property                  | Description                                  |
| ------------------------- | -------------------------------------------- |
| Built-in task options     | `options` and `optionsFrom`                  |
| Plugin task settings      | `settings` and `settingsFrom`                |
| env                       | Can reference parent `env` variables         |
| imports                   | Values in imports and declared files         |
| pipeline.runner.tags      | Build node tags                              |
| pipeline.docker           | `volumes`, `image` and `build`               |
| stage.image and job.image | Environment image configuration              |
| ifModify                  | File change conditions                       |
| name                      | `pipeline.name`, `stage.name` and `job.name` |
| lock.key                  | Lock key name                                |
| allowFailure              | Allow failure configuration                  |

#### Built-in Task Example

```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
```

**Final options content**:

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

#### Plugin Task Example

```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
```

**Final settings content**:

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

#### settingsFrom in Dockerfile

`settingsFrom` specified in Dockerfile `LABEL` also supports variable substitution:

```dockerfile
FROM node:20

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

#### env Example

Property values declared under env can reference variables from parent env for substitution:

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

#### imports Example

Property values in imports and in declared files will undergo variable substitution. If imports is an array,
variables declared in earlier files are effective for later array elements.

In the example below, `env1.yml` declares `address: env2.yml`,
so `$address` is replaced with `env2.yml`,
which then imports variables from `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 Example

Inject the architecture name via the pipeline name `CNB_PIPELINE_NAME` to declare independent
pipelines for different architectures:

```yaml title=".cnb.yml"
main:
  push:
    # amd64 becomes the value of the built-in environment variable 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 becomes the value of the built-in environment variable 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 Example

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

#### ifModify Example

Inject the module name via the pipeline name `CNB_PIPELINE_NAME`; only compile the module when its code changes:

```yaml title=".cnb.yml"
# Only compile corresponding module when code under different modules has changes
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 Example

Property values of pipeline.name, stage.name and job.name will undergo variable substitution:

```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 Example

Import the lock key name from the secret file via `imports`, ensuring only one pipeline holds the lock at a time:

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

```yaml title=".cnb.yml"
main:
  push:
    # Of these two pipelines, one acquires the lock and executes successfully, the other fails to acquire the lock
    - 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 Example

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

### Preventing Variable Substitution

If you don't want `$env_name` to be replaced, you can prevent substitution via `\$`:

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

## Limitations

Environment variables have the following limitations:

| Limitation Item       | Description                                                        |
| --------------------- | ------------------------------------------------------------------ |
| Variable name format  | Letters, numbers, and underscores only; cannot start with a number |
| Variable value length | Cannot exceed `100KiB`                                             |

::: warning Note
Variables that don't meet the above rules will be ignored.
:::

## Proxy Environment Variable Auto-Sync

When a pipeline runs inside a Docker container, the following proxy-related environment
variables are automatically synced from the Runner host into the container,
without any manual declaration in `.cnb.yml`:

| Variable      | Description                                 |
| ------------- | ------------------------------------------- |
| `HTTP_PROXY`  | HTTP proxy address                          |
| `HTTPS_PROXY` | HTTPS proxy address                         |
| `NO_PROXY`    | Addresses that bypass the proxy             |
| `http_proxy`  | HTTP proxy address (lowercase)              |
| `https_proxy` | HTTPS proxy address (lowercase)             |
| `no_proxy`    | Addresses that bypass the proxy (lowercase) |

::: tip Value Rules
When not declared in `env`, these proxy variables automatically inherit the Runner host
configuration, so no manual setup is needed. If declared in `env`, the user-declared value
takes precedence.
:::
