4. 部署工程环境准备

Nextflow 的 CI/CD 自动化部署并不是从写 nextflow.config 开始的,而是从“让流水线能在本地跑通、在容器里可复现、在版本控制中可追溯、在 CI/CD 平台上可自动触发”这四件事开始的。本节围绕这四层环境,给出一个可落地的最小化配置方案,目标是:任何一名团队成员克隆仓库后,能在 30 分钟内复现一次完整的本地运行,并让同一条命令在 CI 平台上得到一致结果。

一、本地开发环境:JDK、Nextflow 与目录约定

Nextflow 基于 JVM,因此第一件事是确定 JDK 版本。推荐使用 JDK 17(LTS),并在项目根目录放置 .java-version 或 .sdkmanrc 固定版本,避免“我本地能跑、CI 上跑不了”。Nextflow 本身建议通过自举脚本安装,并锁定版本号,而不是使用 latest:

# 安装并锁定 Nextflow 版本
export NXF_VER=24.04.2
curl -s https://get.nextflow.io | bash
./nextflow -version
# 输出示例:version 24.04.2 build 5900

目录结构建议在项目初始化时就固定下来,让本地与 CI 使用同一套路径语义:

my-pipeline/
├── main.nf              # 入口流程
├── nextflow.config      # 主配置
├── conf/
│   ├── base.config      # 公共参数
│   ├── local.config     # 本地 profile
│   └── ci.config        # CI profile
├── modules/             # 可复用 process
├── bin/                 # 自定义脚本
├── tests/               # 测试数据与断言
└── .github/workflows/   # CI 定义

本地运行的最小验证命令是 nextflow run main.nf -profile local -stub-run。-stub-run 会用占位命令替换真实计算,只验证流程拓扑、通道连接和参数解析是否正确,通常几秒内完成,非常适合作为提交前的第一道自检。

二、容器运行时:Docker 与 Apptainer 的分工

Nextflow 的 -with-docker、-with-singularity、-with-apptainer 等参数决定了每个 process 在什么运行时中执行。本地开发通常用 Docker,HPC 或受限集群上则用 Apptainer(原 Singularity)。最小化配置的关键是:镜像必须带明确 tag,禁止使用 latest,否则 CI 与本地可能拉到不同内容。

# conf/base.config
process {
    container = 'ghcr.io/example/tools:1.4.0'
    cpus   = 2
    memory = '4 GB'
}

# conf/local.config
docker { enabled = true }

# conf/ci.config
apptainer { enabled = true; cacheDir = '/opt/ci/apptainer-cache' }

验证容器运行时是否就绪,不要只看 docker ps,而应直接拉取项目镜像并运行一次真实命令:

docker pull ghcr.io/example/tools:1.4.0
docker run --rm ghcr.io/example/tools:1.4.0 samtools --version
# 输出示例:samtools 1.19.2

如果这一步失败,后续所有 CI 失败都会指向同一个根因,因此把它作为环境准备阶段的“门禁检查”是值得的。

三、版本控制:分支策略与忽略规则

Nextflow 项目对版本控制有两个特殊要求。第一,work/ 目录会随运行不断膨胀,必须加入 .gitignore;第二,.nextflow/ 缓存和 nextflow.log* 也不应提交。最小 .gitignore 如下:

work/
.nextflow/
.nextflow.log*
nextflow.log*
*.html
results/

分支策略建议采用“主干 + 短生命周期特性分支”:main 始终保持可运行,任何合并到 main 的提交都必须通过 CI;特性分支命名如 feat/add-fastqc,合并前由 CI 自动跑 -stub-run 与单元测试。这样版本控制不只是存代码,而是成为 CI 的触发源和回滚依据。

四、CI/CD 平台账号与权限:最小化原则

以 GitHub Actions 为例,最小权限配置包含三点:仓库级 token 只读、镜像仓库凭据单独存放、运行环境不暴露长期密钥。在仓库 Settings → Actions → General 中,将 Workflow permissions 设为 Read repository contents;推送镜像时使用 GITHUB_TOKEN 或短期 OIDC 凭据,而不是个人 PAT。

# .github/workflows/ci.yml(节选)
permissions:
  contents: read
  packages: write

jobs:
  stub-run:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-java@v4
        with: { java-version: '17', distribution: 'temurin' }
      - run: curl -s https://get.nextflow.io | bash
      - run: ./nextflow run main.nf -profile ci -stub-run

权限最小化的判断标准很简单:如果 CI 被恶意提交利用,攻击者最多只能读取代码和写入本仓库的包,无法访问其他仓库、云账号或生产环境。凡是超出这个范围的权限,都应通过 OIDC 临时换取,而不是长期存放。

Nextflow CI/CD 环境准备四层最小化配置 本地可复现 → 容器可锁定 → 版本可追溯 → 权限可收敛 ① 本地开发环境 JDK 17 (LTS) .sdkmanrc 锁定 Nextflow 24.04.2 NXF_VER 固定 目录约定 main.nf / conf / modules 自检命令 -stub-run 秒级验证 门禁:本地跑通 再进入下一层 ② 容器运行时 Docker(本地) -with-docker Apptainer(CI/HPC) -with-apptainer 镜像 tag 锁定 禁止 latest tools:1.4.0 samtools 1.19.2 门禁:镜像可拉取 命令可执行 ③ 版本控制 main 分支 始终可运行 feat/* 分支 短生命周期 .gitignore work/ .nextflow/ 提交即触发 CI 自动运行 门禁:可追溯 可回滚 ④ CI/CD 权限 contents: read 只读代码 packages: write 仅写本仓库包 OIDC 临时凭据 不存长期密钥 GITHUB_TOKEN 替代个人 PAT 门禁:越权不可达 爆炸半径最小 最小化原则:每层只保留“跑通 + 可复现 + 可追溯 + 可收敛”所必需的配置,超出部分通过临时凭据或按需安装获得。

五、把四层串成一次可复现的验证

环境准备是否完成,不靠“感觉装好了”,而靠一条端到端命令。建议在项目根目录执行:

nextflow run main.nf -profile local -stub-run -ansi-log false
# 观察输出中每个 process 的 [stub] 标记与 exit status 0
# 若出现 "Cannot find container" 或 "Unknown profile",说明对应层未就绪

判断依据可以归纳为三条:第一,-stub-run 在 60 秒内退出且退出码为 0;第二,nextflow.log 中不出现容器拉取失败或 profile 解析错误;第三,把同一条命令原样复制到 CI 的 ci profile 下,结果一致。三条同时满足,才说明本地、容器、版本控制、CI 平台四层已经对齐,可以进入后续的流水线编排与部署阶段。

如果团队使用 GitLab CI 或 Jenkins,把 permissions 一节替换为对应的 CI_JOB_TOKEN 只读配置或 Jenkins 凭据绑定即可,其余三层(JDK、Nextflow、容器 tag、.gitignore)完全通用。