配置文件基础:nextflow.config 的格式与作用

我个人习惯把 nextflow.config 比作流水线的“总开关”。你跑任何一个 pipeline,它都会先去找这个文件,把里面写的规则、参数、资源定义全部加载进来。说白了,没有这个文件,你的 pipeline 就只能用默认值跑,但真实项目里哪有什么“默认环境”?本地、集群、云,资源差远了,必须靠 config 来区分。

我在项目中遇到过好几次:同事把配置直接写在 main.nf 里,换环境就改代码。结果代码库一团乱,回滚都不知道该回哪个版本。其实 nextflow.config 就是专门干这个的——它把运行时的行为和业务逻辑彻底分开,你修改资源限制、队列、内存,完全不用动主脚本。

文件位置与加载顺序

nextflow 会按固定顺序找这个文件,不是随便放哪都行。它的搜索路径是这样的:

  1. 当前工作目录下的 nextflow.config
  2. ~/.nextflow/config(用户级全局配置)
  3. 项目根目录下的 nextflow.config(如果你给 pipeline 指定了 -C 参数,那就是它)
  4. 环境变量 NXF_CONFIG 指向的文件

优先级是反着的:后面加载的会覆盖前面的。嗯,这里要注意:谁在后面谁说了算。我记得有次调试半天,发现本地跑一直用公司的全局配置,就是因为 ~/.nextflow/config 里的默认队列设置被我忽略了。

我的习惯:开发阶段永远在项目根目录放一份 nextflow.config,不依赖用户级配置。这样拉个新仓库就能跑,不用手配环境。

文件格式与语法

nextflow.config 的语法很简洁,基于 Groovy 的属性风格。但你别被它骗了,它能干的事比普通属性文件多得多——支持表达式、变量引用、条件判断,甚至能调用 Java 类。

最基本的写法就是键值对:

process.executor = 'slurm'
process.queue = 'normal'
process.memory = '8 GB'
process.cpus = 4

params.input = '/data/samples.csv'
params.outdir = './results'

docker.enabled = true
docker.container = 'biocontainers/vcftools:v0.1.16'

你看到了,它用的是点号分隔的层级结构。process 下面挂 executor、queue、memory;params 下面挂用户自定义参数。这种写法最大的好处是:你可以按作用域组织,不会乱。

除了属性赋值,它还支持 includeConfig,把一个大配置拆成多个小文件:

includeConfig 'conf/base.config'
includeConfig 'conf/resources.config'
includeConfig 'conf/cluster.config'

这样每个文件只管一件事:base.config 管镜像和默认资源,resources.config 管不同进程的资源模板,cluster.config 管集群调度。我建议你从第一天就这么分,而不是堆在一个文件里。我有一个同事曾经把一个 2000 行的 config 文件交给我,那感觉就像看一坨意大利面……

主要作用:三层分离

nextflow.config 的核心价值,在我看来就是三层分离:

层级 典型配置项 示例
基础设施层 executor, queue, workDir, cache process.executor = 'sge'
资源层 memory, cpus, time, container withName: myProcess { cpus = 2; memory = '4 GB' }
应用层 params.*, env.*, publishDir params.genome = 'hg38'

为什么要分这么清楚?因为改调度器只改基础设施层,加机器只改资源层,调参数只改应用层。你想想看,如果都混在一起,一个改动了可能影响到别的地方,那维护起来多累。

一句话总结:nextflow.config 是运行时策略的配置中心,不是业务代码。它决定了 pipeline “在哪跑”、“跑多久”、“占多少资源”,但不管“跑什么”。

环境配置的基石:Profiles

既然要做多环境配置,你就得有一个办法把不同环境的配置包起来。Nextflow 的 profiles 就是干这个的。一个 profile 其实就是一组配置的集合,你启动的时候用 -profile 指定用哪一套。

profiles {
    local {
        process.executor = 'local'
        workDir = './work'
    }
    cluster {
        includeConfig 'conf/cluster.config'
        process.executor = 'sge'
        process.queue = 'long'
        workDir = '/scratch/$USER/nf-work'
    }
    aws {
        includeConfig 'conf/aws.config'
        process.executor = 'awsbatch'
        workDir = 's3://my-bucket/nf-work'
    }
}

// 全局默认值
process.executor = 'local'
params.outdir = './results'

这里我画了三个 profile:local 给开发用,cluster 给学校集群用,aws 给云端用。注意看,我在 aws 里专门 include 了一个外部配置,把 S3 路径、IAM 角色这些塞进去,主文件保持干净。

我曾经踩过一个坑:在 cluster profile 里只改了 executor 和 queue,忘了改 workDir。结果作业提交到集群,中间文件却写到了本地的 ./work 目录,导致作业因为跨节点 NFS 挂载问题失败。所以后来我养成了习惯——每个 profile 都要显式覆盖 workDir。

重要提醒:profiles 中的配置会覆盖文件顶部定义的全局配置。如果你在文件顶部定义了一个 process.memory = '8 GB',然后在某个 profile 里设成了 '16 GB',那启动时只要用了这个 profile,就是以 16 GB 为准。顺序是:profile 内部按出现顺序覆盖,最后看启动命令的 -C 和 -profile 参数。

下面这张图展示了 nextflow 启动时配置文件如何被加载和应用:

nextflow.config 加载与覆盖流程 nextflow run 寻找 nextflow.config(搜索顺序) 项目目录 用户 ~/.nextflow 环境变量 NXF_CONFIG 合并配置(后加载覆盖前) 最终配置 → 执行 pipeline

从图里能很清楚看到:nextflow 启动后先按顺序搜三个位置的 config 文件,然后合并成一份最终配置。如果配置中定义了 profiles,还要根据传入的 profile 名字再覆盖一次对应块里的内容。这就是多环境配置的底层逻辑。

我特别喜欢用 includeConfig 来管理那些跟密码或 Access Key 有关的配置。比如 AWS 的 aws.config 我单独放,里面用环境变量读取密钥,不上传到 Git。这样既安全又能让不同项目复用同一个云配置模板。

小技巧:在 nextflow.config 里可以打印调试信息来验证配置是否加载成功。比如在最下面写一行:
println "Loaded config: executor=${process.executor}, workDir=${workDir}"
启动 pipeline 时就能看到它输出的值。我曾经用这招抓到了全局配置覆盖项目配置的 bug。

总结一下核心概念

  • 格式:Groovy 属性风格,点号层级,支持 includeConfig 和表达式。
  • 作用:分离基础设施、资源、应用三层,让环境切换与业务逻辑解耦。
  • 加载顺序:内置优先级 + 用户全局 + 项目根目录 + 环境变量,后加载覆盖前。
  • profiles:多环境配置的核心结构,每个 profile 是一套完整配置,通过 -profile 选择激活。

掌握了这些基础,后面讲具体的多环境拆分方案时你就能理解为什么这么设计了。说白了,nextflow.config 就是给流水线穿的一件“万能外套”,换环境就换外套,但内衣(业务代码)不用换。