配置文件基础:nextflow.config 的格式与作用
我个人习惯把 nextflow.config 比作流水线的“总开关”。你跑任何一个 pipeline,它都会先去找这个文件,把里面写的规则、参数、资源定义全部加载进来。说白了,没有这个文件,你的 pipeline 就只能用默认值跑,但真实项目里哪有什么“默认环境”?本地、集群、云,资源差远了,必须靠 config 来区分。
我在项目中遇到过好几次:同事把配置直接写在 main.nf 里,换环境就改代码。结果代码库一团乱,回滚都不知道该回哪个版本。其实 nextflow.config 就是专门干这个的——它把运行时的行为和业务逻辑彻底分开,你修改资源限制、队列、内存,完全不用动主脚本。
文件位置与加载顺序
nextflow 会按固定顺序找这个文件,不是随便放哪都行。它的搜索路径是这样的:
- 当前工作目录下的 nextflow.config
- ~/.nextflow/config(用户级全局配置)
- 项目根目录下的 nextflow.config(如果你给 pipeline 指定了 -C 参数,那就是它)
- 环境变量 NXF_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' |
为什么要分这么清楚?因为改调度器只改基础设施层,加机器只改资源层,调参数只改应用层。你想想看,如果都混在一起,一个改动了可能影响到别的地方,那维护起来多累。
环境配置的基石: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。
process.memory = '8 GB',然后在某个 profile 里设成了 '16 GB',那启动时只要用了这个 profile,就是以 16 GB 为准。顺序是:profile 内部按出现顺序覆盖,最后看启动命令的 -C 和 -profile 参数。
下面这张图展示了 nextflow 启动时配置文件如何被加载和应用:
从图里能很清楚看到:nextflow 启动后先按顺序搜三个位置的 config 文件,然后合并成一份最终配置。如果配置中定义了 profiles,还要根据传入的 profile 名字再覆盖一次对应块里的内容。这就是多环境配置的底层逻辑。
我特别喜欢用 includeConfig 来管理那些跟密码或 Access Key 有关的配置。比如 AWS 的 aws.config 我单独放,里面用环境变量读取密钥,不上传到 Git。这样既安全又能让不同项目复用同一个云配置模板。
println "Loaded config: executor=${process.executor}, workDir=${workDir}"
启动 pipeline 时就能看到它输出的值。我曾经用这招抓到了全局配置覆盖项目配置的 bug。
总结一下核心概念
- 格式:Groovy 属性风格,点号层级,支持 includeConfig 和表达式。
- 作用:分离基础设施、资源、应用三层,让环境切换与业务逻辑解耦。
- 加载顺序:内置优先级 + 用户全局 + 项目根目录 + 环境变量,后加载覆盖前。
- profiles:多环境配置的核心结构,每个 profile 是一套完整配置,通过
-profile选择激活。
掌握了这些基础,后面讲具体的多环境拆分方案时你就能理解为什么这么设计了。说白了,nextflow.config 就是给流水线穿的一件“万能外套”,换环境就换外套,但内衣(业务代码)不用换。