---
url: /zh/faq.md
description: |
  收录 CNB 平台常见问题与解答， 涵盖流水线触发与配置、执行与调试、 构建错误排查、缓存与环境等典型场景。
---
遇到问题，建议先查阅以下文档或 [feedback]()。

若未找到答案，请在 [feedback]() 中提交 `issue`。

## 触发与配置

### 流水线为什么没触发？

定位流水线未触发的问题，需先理解从触发到执行的流程。

以 `push` 事件为例：

```mermaid
flowchart LR
    push(git push) --> config(CI 读取对应分支下 .cnb.yml) --> event(获取对应分支 push 事件配置)
    --> skip(skip 检测) --> 流水线执行
```

> 其中 `skip 检测` 包括 `ifNewBranch`、`ifModify`，参考[语法](./build/grammar.md#Pipeline-ifModify)。

可按以下流程逐层排查：

1. 代码是否 push 到远端？
2. 对应分支是否有 `.cnb.yml`？
   1. include 的文件是否有权限
   2. 配置内容无格式、语法问题
3. `.cnb.yml` 是否声明了当前分支的 `push` 事件流水线？
4. 是否命中了 skip 检测？

> CNB 默认分支通常为 `main`，部分从其他平台迁移的仓库可能为 `master`，请注意区分。

### 在一个分支配置了另一个分支的流水线，为什么没生效？

`.cnb.yml` 中的流水线配置**只对当前分支生效**。在 A 分支配置 B 分支的流水线，
B 分支不会按此配置执行，实际取决于各分支自身的 `.cnb.yml`。

例如，在 `main` 分支的 `.cnb.yml` 中配置了 `dev` 的 push，
当 `dev` 分支 push 时，系统会读取 `dev` 分支自身的 `.cnb.yml`，而非 `main` 分支的。

详细说明参考 [触发分支](./build/trigger-rule.md#trigger-branch)。

### Fork 仓库为什么不自动触发流水线？

出于安全考虑，**Fork 后的仓库默认不会自动触发流水线**
（包括 Git 操作事件、定时任务事件、Issue 事件）。

如需启用，可在**源仓库**页面依次进入 **设置 → 云原生构建**，
勾选「Fork 的仓库默认允许自动触发」。

> 注意：即使启用了自动触发，
> Fork 仓库流水线中的 `CNB_TOKEN` 权限也会被限制在当前仓库范围内。

详细说明参考 [Fork 仓库触发限制](./build/trigger-rule.md#fork-仓库触发限制)。

### 如何跳过流水线执行？

在 `push`、`commit.add` 和 `branch.create` 事件中，可通过以下两种方式跳过：

1. 在 commit message 中添加 `[ci skip]` 或 `[skip ci]`：

```bash
git commit -m "docs: update readme [ci skip]"
git push origin main
```

2. 使用 git push 选项：

```bash
git push origin main -o ci.skip
```

详细说明参考 [跳过流水线](./build/skip-pipeline.md)。

### 为什么插件任务拿不到 env / imports 设置的环境变量？

这是脚本任务和插件任务的核心区别之一。
**插件任务不会接收通过 `env` 或 `imports` 声明的自定义环境变量**，
只能使用 CNB 内置的系统环境变量。

插件任务的参数应通过 `settings` 传递：

```yaml title=".cnb.yml"
# ❌ 错误：env 声明的变量不会传递给插件
- name: wrong
  image: plugins/npm
  env:
    PLUGIN_USERNAME: $NPM_USER

# ✅ 正确：通过 settings 传递参数
- name: correct
  image: plugins/npm
  settings:
    username: $NPM_USER
```

详细对比参考 [脚本任务 vs 插件任务](./build/script-vs-plugin.md)。

## 执行与调试

### 为什么本地正常但 CI 上构建失败？

定位此问题，需先明确本地与 CI 环境的区别：

|        | 本地                     | CI 环境                   |
| ------ | ----------------------- | ------------------------ |
| 网络   | 本地网络（比如一些办公内网） | CI 机器所在网络            |
| 文件   | 本地整个目录下所有文件      | Git 仓库对应分支代码       |
| 代码   | 源分支代码                | pull\_request 事件下源分支与目标分支预合并后的代码 |
| 环境   | 原生                     | 指定的 Docker 容器环境     |
| Shell  | 本地指定                  | sh                       |

了解差异后，可依次排查：

1. 是否有文件未提交？
2. 构建依赖的文件是否命中 `.gitignore`？
3. 是否依赖本地才有的资源（如本地接口、凭证等）？
4. CI 声明的 [cpus](./build/grammar.md#runner) 是否满足要求？
5. 在本地运行相同镜像，获得与 CI 一致的构建环境进行调试。
6. 流水线是否由 `pull_request` 事件触发？
   该事件跑在源分支与目标分支**预合并**后的代码上，
   而本地通常只跑源分支代码，目标分支的新提交可能在本地尚未拉取，导致行为不一致。

`云原生构建` 的默认镜像为 [\_\_ENV\_\_CNB\_DEFAULT\_DOCKER\_IMAGE](https://cnb.cool/cnb/cool/default-build-env)。

可以执行以下命令，进入默认 CI 环境进行调试：

```bash
docker run --rm -it -v $(pwd):$(pwd) -w $(pwd) cnbcool/default-build-env sh
```

若声明了其他镜像作为构建环境，将上述命令中 `cnbcool/default-build-env` 替换为对应镜像地址即可。

### 依赖版本未锁定导致 CI 构建失败？

这也是「本地正常但 CI 失败」的常见原因之一。以 npm 为例，
如果 `package.json` 中依赖版本使用了 `^` 或 `~` 前缀（如 `"axios": "^1.0.0"`），
而项目没有提交 `package-lock.json`、`yarn.lock` 等锁文件，
则每次 `npm install` 都会拉取符合版本范围的**最新版本**。

当某个依赖发布了不兼容的新版本时：

* **本地不受影响**：`node_modules` 已缓存旧版本，不会重新拉取。
* **CI 失败**：每次构建都是全新安装，拉到新版本后可能因 API 变更、行为变更等导致编译或测试失败。

> 上方排查清单中的第 1、2 点（未提交的文件、`.gitignore`）也应涵盖锁文件检查。

**解决方案**：

1. 将锁文件提交到代码仓库（`package-lock.json`、`yarn.lock` 或 `pnpm-lock.yaml`）。
2. CI 中使用 `npm ci` 代替 `npm install`，前者严格按照锁文件安装，不会更新版本。
3. 如需锁定工具版本（如 Node.js），在 `.cnb.yml` 中指定对应镜像版本：

```yaml title=".cnb.yml"
main:
  push:
    - docker:
        image: node:20 # 指定 Node.js 版本
      stages:
        - name: install
          script: npm ci
```

> 类似问题也适用于其他包管理工具：
> pip（提交 `requirements.txt` 或使用 `pipenv`/`poetry` 锁文件）、
> Maven（使用 `mvn versions:lock-snapshots`）、
> Go（提交 `go.sum`）等。

### 如何登录到流水线容器调试？

参考 [登录调试](./build/login-debug.md)。

### 流水线执行脚本与登录调试结果不一致？

流水线默认使用 `sh`，登录调试使用 `bash`，两者语法存在差异。

如果确认流水线容器支持 `bash`（默认容器支持，自定义容器可能不支持），
可以将执行脚本改为：

```bash
bash xxx.sh
# 或
bash -c '原来的语句'
```

### 流水线超时无输出？

一个 Job 超过 10 分钟无日志输出会被终止。可考虑增加日志输出，例如为 `npm install` 添加 `--verbose` 参数。

这与 Job 的 `timeout` 超时不同，无法通过配置修改。

### 为什么 PR 流水线中 docker push / git push 失败？

PR 相关事件属于[不可信事件](./build/trigger-rule.md#untrusted-events)，
流水线中 `CNB_TOKEN` 权限受限——代码和制品库仅有**只读**权限，
`docker push`、`git push` 等写操作会失败。

出于安全考虑：PR 源分支代码可能被未授权用户修改，
赋予写权限可能导致恶意代码注入或构建产物被篡改。

**解决方案**：将涉及推送的任务放到
`push`、`pull_request.target` 或 `tag_push` 等可信事件中执行即可。

### 为什么公开仓库流水线无法访问私有资源？

如果流水线所在仓库是公开仓库，流水线中的 `CNB_TOKEN` 只能访问**公开资源**，
不能访问私有代码仓库、私有制品仓库等私有资源。

这是为了避免公开仓库中的流水线代码获得私有资源访问权限。

若需要访问私有资源，可选择以下方式：

1. 在私有仓库中触发流水线。
2. 调整目标资源的访问范围。
3. 自行创建具备所需权限的个人令牌或部署令牌，并在流水线中使用。

详细说明参考 [CNB\_TOKEN 安全限制](./build/build-in-env.md#安全限制)。

### 为什么没改代码，流水线还是失败了？

可检查依赖的其他资源是否有变动：

1. 插件任务镜像版本是否为 `latest`，镜像是否有变动。
2. CI 配置文件引用的其他仓库文件是否有变动。
3. 可能是网络波动，尝试 Rebuild。

### 如何查看流水线完整日志？

CI 服务将插件任务和脚本任务发送至 Runner 执行。任务日志过长时会被截断返回。

阶段会汇总其下所有任务的日志，过长时也会截断，以便在日志页面更好地展示。

构建完成后，可在日志页面流水线右上角点击日志下载按钮查看完整日志。

## 缓存与环境

### 为什么缓存（volumes）有时不生效？

流水线的文件缓存（`volumes`）是**节点级别**的，存储在构建节点本地磁盘。
默认情况下，每个仓库的流水线分配到固定 3 个节点执行，因此：

1. **跨节点不共享**：上次构建在节点 A 写入的缓存，
   这次如果分配到节点 B，则无法命中。
2. **节点可能变化**：随着平台扩缩容，分配的节点可能发生变化，
   旧节点上的缓存不会自动迁移。

如需每次构建都能使用同一份缓存，
可以使用 [docker:cache](./build/internal-steps/docker/cache.md)
内置任务将缓存构建为 Docker 镜像，实现跨节点缓存。

详细说明参考 [流水线缓存](./build/pipeline-cache.md)。

### 为什么不提供固定的出口 IP？

CNB 的出口 IP 是动态的，可能随时间变化。原因：

1. **运维弹性**：平台根据负载自动扩缩容、迁移节点，出口 IP 随之动态变化。
2. **安全建议**：不建议对 CNB 出口 IP 加白名单。
   CNB 是公开平台，对出口 IP 加白等同于对公网加白，存在安全风险。

替代方案（需要固定 IP 访问内部服务时）：

* **私有代理**：配置自有代理服务器，通过代理路由请求，使用代理的固定 IP
* **跳板机**：通过 SSH 跳板机连接目标机器执行命令，可使用
  [cnbcool/ssh](https://cnb.cool/cnb/plugins/cnbcool/ssh)
  插件。[参考示例](https://cnb.cool/examples/ecosystem/springboot-maven-docker-jumpserver)
* **腾讯云插件**：推荐 [tencentcom/tcloud-cmd](https://cnb.cool/cnb/plugins/tencentcom/tcloud-cmd) 插件，
  可远程执行命令，无需白名单配置

### 如何构建多架构/多平台镜像？

若需要使用 `buildx` 构建多种架构/平台的镜像，平台默认支持多架构构建能力，
直接使用 `docker buildx` 即可，无需额外配置：

```yaml title=".cnb.yml"
main:
  push:
    - services:
        - docker
      env:
        IMAGE_TAG: ${CNB_DOCKER_REGISTRY}/${CNB_REPO_SLUG_LOWERCASE}:latest
      stages:
        - name: docker login
          script: docker login -u ${CNB_TOKEN_USER_NAME} -p "${CNB_TOKEN}" ${CNB_DOCKER_REGISTRY}
        - name: docker build and push
          script: docker buildx build -t ${IMAGE_TAG} --platform linux/amd64,linux/arm64,linux/arm/v7 --push .
```

> **注意**：一般情况下**无需**开启 `rootlessBuildkitd`，已默认支持 Docker 多架构构建。

```yaml title=".cnb.yml"
main:
  push:
    - docker:
        image: golang:1.24
      services:
        - name: docker
          options:
            rootlessBuildkitd:
              enabled: true
      env:
        IMAGE_TAG: ${CNB_DOCKER_REGISTRY}/${CNB_REPO_SLUG_LOWERCASE}:latest
      stages:
        - name: go build
          script: ./build.sh
        - name: docker login
          script: docker login -u ${CNB_TOKEN_USER_NAME} -p "${CNB_TOKEN}" ${CNB_DOCKER_REGISTRY}
        - name: docker build and push
          script: docker buildx build -t ${IMAGE_TAG} --platform linux/amd64,linux/arm64 --push .
```

### 流水线中运行 Docker 镜像时报错 UID/GID 超过 65535？

如果在流水线中遇到类似以下错误：

```text
docker: failed to extract layer ... failed to Lchown ... for UID 100000, GID 100000: lchown ...: invalid argument
(Hint: try increasing the number of subordinate IDs in /etc/subuid and /etc/subgid)
```

**原因**：

CNB 的 Docker 采用 `uidmap` 技术，将容器内 UID 范围限制在 0-65535。
当镜像中存在 UID/GID 超过 65535 的文件时，Docker 无法正确映射，
导致镜像层解压失败。

**解决方案**：

重新制作镜像，清理这类文件或改为正常 UID/GID，避免使用包含超过 65535 的 UID/GID 的基础镜像。

### 为什么流水线的仓库存储用量跟页面上显示不一致？

流水线上看到的仓库大小通常与页面显示接近。

但对于所有公开的 `Fork 仓库`，为加速构建和优化存储，
所有 Fork 仓库在构建节点上会复用其祖先仓库的 `.git` 目录，
再通过 `OverlayFS` 技术裂变出多份副本供不同仓库使用。
因此流水线上看到的仓库大小实际包含祖先仓库和所有子孙仓库。

对于普通仓库，页面上触发仓库 GC 后，流水线上仓库大小不会立即变化，
需等待构建机缓存 GC 逻辑运行后才与页面一致。

## 代码仓库相关

### 如何解决代码合并冲突？{#dai-ma-he-bing-chong-tu}

创建 PR 或在 PR 相关事件构建中遇到代码冲突时，可通过以下命令解决：

```bash
git fetch origin          # 从远端仓库获取更新
git rebase -i origin/main # 实际的目标分支名

# 本地处理冲突
git commit  # 根据实际情况提交到本地仓库
git push -f # 推送到远端仓库
```

### 如何彻底删除 Git 仓库里的文件，释放空间？

Git 仓库支持恢复任意历史提交，`.git` 目录会存储所有历史文件。仅删除工作目录中的文件不会释放空间。

要彻底删除 Git 仓库中的文件并释放空间，可通过以下两种方法：

**方法一**（最简单）：在远端新建仓库，复制需要的文件过去，再改名为原仓库名。

**方法二**（较复杂）：通过 `git filter-branch` 命令或第三方工具
（如 `BFG Repo-Cleaner`）在本地彻底删除对应文件后，
强制推送到远端，再触发远端仓库 GC 流程。
