Docker 与 Nextflow 整合:容器化流程¶
适用人群:已了解 Docker 基础和 Nextflow 基础的生信工程师 前置知识:564_Docker生信实战、Nextflow DSL2 基础 预计阅读 + 实操时间:3-4 小时
一、为什么要 Nextflow + Docker?¶
1.1 可复现性的终极方案¶
单独用 Nextflow 或单独用 Docker 都有局限:
| 方案 | 解决了什么 | 没解决什么 |
|---|---|---|
| 只用 Nextflow | 流程自动化、并行调度、断点续跑 | 软件版本不一致、环境依赖冲突 |
| 只用 Docker | 软件环境隔离、版本锁定 | 手动串接步骤、不能自动并行 |
| Nextflow + Docker | 两个问题都解决了 | 无 |
白话理解: - Nextflow = 导演,负责安排"谁先跑、谁后跑、谁可以同时跑" - Docker = 演员的化妆间,保证每个演员(工具)用的都是指定版本的化妆品(依赖库) - 两者结合 = 一部电影的完整拍摄方案:既安排了拍摄顺序,又保证了演员造型一致
1.2 实际好处¶
这就是 可复现性(Reproducibility),是发高分文章的基本要求。
二、Nextflow 使用 Docker 容器的两种方式¶
2.1 方式一:process 级别指定 container¶
每个 process 使用不同的容器——推荐方式,粒度最细。
// main.nf —— 每个步骤用自己的容器
process FASTP {
container 'quay.io/biocontainers/fastp:1.3.3--h5f740d0_0' // 指定 fastp 容器
input:
tuple val(sample_id), path(reads) // 输入:样本ID + 读段文件
output:
tuple val(sample_id), path("*_clean.fq.gz"), emit: clean_reads // 输出:质控后的读段
path("${sample_id}_fastp.json"), emit: report // 输出:质控报告
script:
"""
fastp \
-i ${reads[0]} \
-I ${reads[1]} \
-o ${sample_id}_R1_clean.fq.gz \
-O ${sample_id}_R2_clean.fq.gz \
-j ${sample_id}_fastp.json \
-w 4
"""
// -i/-I:输入的双端读段
// -o/-O:输出的质控后读段
// -j:JSON 格式的质控报告
// -w 4:使用 4 个线程
}
process BOWTIE2_REMOVE_HOST {
container 'quay.io/biocontainers/bowtie2:2.5.5--he96a11b_0' // 指定 bowtie2 容器
input:
tuple val(sample_id), path(reads) // 输入:质控后的读段
path(bt2_index) // 输入:宿主基因组索引
output:
tuple val(sample_id), path("*_nohost.fq.gz"), emit: clean_reads // 输出:去宿主后的读段
script:
"""
bowtie2 \
-x ${bt2_index}/${bt2_index.simpleName} \
-1 ${reads[0]} \
-2 ${reads[1]} \
--un-conc-gz ${sample_id}_nohost.fq.gz \
-S /dev/null \
-p 8
"""
// -x:索引路径前缀
// --un-conc-gz:输出未比对上(非宿主)的读段
// -S /dev/null:不保存 SAM 输出(只要去宿主的结果)
// -p 8:使用 8 个线程
}
process KRAKEN2_CLASSIFY {
container 'quay.io/biocontainers/kraken2:2.17.1--h4ac6f70_0' // 指定 kraken2 容器
input:
tuple val(sample_id), path(reads) // 输入:去宿主后的读段
path(kraken2_db) // 输入:Kraken2 数据库
output:
tuple val(sample_id), path("*_kraken2.txt"), emit: report // 输出:分类报告
tuple val(sample_id), path("*_kraken2.out"), emit: output // 输出:逐条序列分类结果
script:
"""
kraken2 \
--db ${kraken2_db} \
--paired ${reads[0]} ${reads[1]} \
--output ${sample_id}_kraken2.out \
--report ${sample_id}_kraken2.txt \
--threads 8
"""
// --db:Kraken2 数据库路径
// --paired:双端读段模式
// --output:每条序列的分类结果
// --report:汇总分类报告
}
2.2 方式二:nextflow.config 中全局指定¶
所有 process 使用同一个容器——适合所有工具装在一个镜像里的情况。
// nextflow.config —— 全局指定容器
docker {
enabled = true // 启用 Docker
runOptions = '-u $(id -u):$(id -g)' // 以当前用户身份运行(避免权限问题)
}
process {
container = 'metagenome-pipeline:v1.0' // 所有 process 默认使用这个镜像
}
2.3 两种方式对比¶
| 特性 | process 级别指定 | 全局指定 |
|---|---|---|
| 灵活性 | 高,每个步骤用最合适的容器 | 低,所有步骤共用一个 |
| 镜像体积 | 每个镜像小(只含一个工具) | 一个大镜像(包含所有工具) |
| 维护成本 | 需要管理多个镜像版本 | 只管一个镜像 |
| 推荐场景 | 正式项目、发表文章 | 快速测试、个人小项目 |
三、实战:容器化宏基因组流程¶
3.1 完整的 main.nf¶
#!/usr/bin/env nextflow
// 宏基因组分析流程——容器化版本
// 流程:质控 → 去宿主 → 物种分类
nextflow.enable.dsl = 2 // 使用 DSL2 语法
// ===== 参数定义 =====
params.reads = "data/*_{R1,R2}.fq.gz" // 输入:双端测序数据(通配符匹配)
params.bt2_index = "ref/human_genome" // Bowtie2 宿主基因组索引路径
params.kraken2_db = "db/kraken2_standard" // Kraken2 数据库路径
params.outdir = "results" // 输出目录
// ===== 日志信息 =====
log.info """
=================================
宏基因组分析流程 (容器化版本)
=================================
输入数据 : ${params.reads}
Bowtie2索引 : ${params.bt2_index}
Kraken2数据库: ${params.kraken2_db}
输出目录 : ${params.outdir}
=================================
""".stripIndent()
// ===== Process 定义 =====
process FASTP {
tag "${sample_id}" // 日志中显示样本名
publishDir "${params.outdir}/01_fastp", // 结果发布到输出目录
mode: 'copy' // 复制模式(不是软链接)
container 'quay.io/biocontainers/fastp:1.3.3--h5f740d0_0'
input:
tuple val(sample_id), path(reads)
output:
tuple val(sample_id), path("${sample_id}_R{1,2}_clean.fq.gz"), emit: clean_reads
path("${sample_id}_fastp.json"), emit: report
path("${sample_id}_fastp.html"), emit: html
script:
"""
fastp \
-i ${reads[0]} \
-I ${reads[1]} \
-o ${sample_id}_R1_clean.fq.gz \
-O ${sample_id}_R2_clean.fq.gz \
-j ${sample_id}_fastp.json \
-h ${sample_id}_fastp.html \
-w ${task.cpus}
"""
}
process BOWTIE2_REMOVE_HOST {
tag "${sample_id}"
publishDir "${params.outdir}/02_remove_host", mode: 'copy'
container 'quay.io/biocontainers/bowtie2:2.5.5--he96a11b_0'
input:
tuple val(sample_id), path(reads)
path(bt2_index)
output:
tuple val(sample_id), path("${sample_id}_nohost_R{1,2}.fq.gz"), emit: clean_reads
path("${sample_id}_bowtie2.log"), emit: log
script:
"""
bowtie2 \
-x ${bt2_index}/genome \
-1 ${reads[0]} \
-2 ${reads[1]} \
--un-conc-gz ${sample_id}_nohost_R%.fq.gz \
-S /dev/null \
-p ${task.cpus} \
2> ${sample_id}_bowtie2.log
"""
// 2>:把标准错误(含比对统计)重定向到日志文件
}
process KRAKEN2_CLASSIFY {
tag "${sample_id}"
publishDir "${params.outdir}/03_kraken2", mode: 'copy'
container 'quay.io/biocontainers/kraken2:2.17.1--h4ac6f70_0'
input:
tuple val(sample_id), path(reads)
path(kraken2_db)
output:
tuple val(sample_id), path("${sample_id}_kraken2.txt"), emit: report
tuple val(sample_id), path("${sample_id}_kraken2.out"), emit: output
script:
"""
kraken2 \
--db ${kraken2_db} \
--paired ${reads[0]} ${reads[1]} \
--output ${sample_id}_kraken2.out \
--report ${sample_id}_kraken2.txt \
--threads ${task.cpus}
"""
}
// ===== 工作流定义 =====
workflow {
// 创建输入 channel:从文件路径匹配双端数据
reads_ch = Channel
.fromFilePairs(params.reads) // 自动配对 R1/R2 文件
// fromFilePairs 会生成 [sample_id, [R1.fq.gz, R2.fq.gz]] 的 tuple
// Bowtie2 索引和 Kraken2 数据库作为路径
bt2_index = Channel.fromPath(params.bt2_index) // Bowtie2 索引目录
kraken2_db = Channel.fromPath(params.kraken2_db) // Kraken2 数据库目录
// 串联流程
FASTP(reads_ch) // 第 1 步:质控
BOWTIE2_REMOVE_HOST(FASTP.out.clean_reads, bt2_index) // 第 2 步:去宿主
KRAKEN2_CLASSIFY(BOWTIE2_REMOVE_HOST.out.clean_reads, kraken2_db) // 第 3 步:物种分类
}
3.2 完整的 nextflow.config¶
// nextflow.config —— 流程配置文件
// ===== Docker 配置 =====
docker {
enabled = true // 启用 Docker 模式
runOptions = '-u $(id -u):$(id -g)' // 用当前用户权限运行容器
// 不加这行的话,容器里创建的文件属于 root,你在宿主机上没法操作
}
// ===== 进程资源配置 =====
process {
// 默认资源
cpus = 4 // 默认 4 核
memory = '8 GB' // 默认 8GB 内存
time = '2h' // 默认最长运行 2 小时
// 针对不同 process 的资源覆盖
withName: 'FASTP' {
cpus = 4 // fastp 4 核就够了
memory = '4 GB'
}
withName: 'BOWTIE2_REMOVE_HOST' {
cpus = 8 // bowtie2 比对吃 CPU
memory = '16 GB'
}
withName: 'KRAKEN2_CLASSIFY' {
cpus = 8 // kraken2 需要多线程
memory = '32 GB' // kraken2 数据库需要大内存
}
}
// ===== 运行 profile =====
profiles {
// 本地电脑运行(默认)
standard {
docker.enabled = true
}
// HPC 集群运行(用 Singularity 替代 Docker)
cluster {
singularity {
enabled = true // 启用 Singularity
autoMounts = true // 自动挂载工作目录
cacheDir = "$HOME/.singularity/cache" // 镜像缓存目录
}
docker.enabled = false // 关闭 Docker
}
// 测试运行(少量数据快速验证)
test {
docker.enabled = true
params.reads = "test_data/*_{R1,R2}.fq.gz"
params.bt2_index = "test_data/ref"
params.kraken2_db = "test_data/minikraken"
}
}
// ===== 报告配置 =====
report {
enabled = true // 生成运行报告
file = "${params.outdir}/report.html"
overwrite = true
}
timeline {
enabled = true // 生成时间线
file = "${params.outdir}/timeline.html"
overwrite = true
}
3.3 运行命令¶
# 标准运行(使用 Docker)
nextflow run main.nf
# Nextflow 会自动拉取每个 process 指定的容器
# 使用测试数据
nextflow run main.nf -profile test
# 使用 test profile 中定义的小数据集
# 指定参数覆盖
nextflow run main.nf \
--reads "my_data/*_{R1,R2}.fq.gz" \ # 覆盖输入路径
--outdir "my_results" # 覆盖输出路径
# 在 HPC 上运行(自动切换到 Singularity)
nextflow run main.nf -profile cluster
四、Singularity/Apptainer 替代方案¶
4.1 为什么 HPC 不能用 Docker?¶
Docker 需要 root 权限(或 docker 组权限)
↓
HPC 集群的用户没有 root 权限(安全原因)
↓
所以 HPC 上用 Singularity/Apptainer(不需要 root)
白话理解:Docker 像一把万能钥匙,什么都能开——这在服务器上太危险了。Singularity/Apptainer 是一把普通钥匙,只能开自己的门——安全,所以 HPC 管理员放心让你用。
4.2 Singularity → Apptainer 的历史¶
singularity命令仍然可用(Apptainer 自动创建了兼容的符号链接)- Nextflow 中配置写
singularity,实际上调用的就是 Apptainer
4.3 自动从 Docker 镜像转换¶
Singularity/Apptainer 最棒的特性:自动把 Docker 镜像转成 Singularity 格式。
# 手动转换(不使用 Nextflow 时)
singularity pull fastp.sif docker://quay.io/biocontainers/fastp:1.3.3--h5f740d0_0
# 从 Docker Hub / Quay.io 拉取 Docker 镜像 → 自动转成 .sif 文件
# 运行 Singularity 容器
singularity exec fastp.sif fastp --version
# exec:在容器里执行命令
# 挂载目录并运行
singularity exec \
--bind /home/pweaz/data:/data \ # 类似 Docker 的 -v 参数
fastp.sif \
fastp -i /data/sample_R1.fq.gz -o /data/clean_R1.fq.gz
4.4 Nextflow + Singularity 配置¶
在 nextflow.config 中只需几行配置,Nextflow 就能自动处理镜像转换:
// nextflow.config —— Singularity 配置
singularity {
enabled = true // 启用 Singularity 模式
autoMounts = true // 自动挂载当前工作目录和输入文件目录
cacheDir = "$HOME/.singularity/cache" // 缓存转换后的 .sif 文件
// Nextflow 会自动把 process 中写的 Docker 镜像地址转成 Singularity 格式
// 你不需要改 main.nf 里的 container 指令!
}
docker {
enabled = false // 关闭 Docker(两者不能同时启用)
}
重点:main.nf 中的 container 指令完全不用改!Nextflow 会自动把 quay.io/biocontainers/fastp:1.3.3--h5f740d0_0 转换为 Singularity 镜像。
# 运行命令也一样
nextflow run main.nf -profile cluster # 使用 cluster profile(启用 Singularity)
# Nextflow 自动做这些事:
# 1. 检查 cacheDir 有没有缓存的 .sif 文件
# 2. 如果没有,从 Docker Hub/Quay.io 拉取并转换
# 3. 用 singularity exec 运行每个 process
五、自定义容器 + Nextflow¶
5.1 用自己的 Dockerfile 构建镜像¶
当 BioContainers 没有你需要的工具组合时,构建自定义镜像:
# Dockerfile.custom_analysis
FROM mambaorg/micromamba:1.5-jammy
USER root
# 安装自定义的工具组合(比如需要 R + 几个 R 包)
RUN micromamba install -y -n base -c bioconda -c conda-forge -c r \
r-base=4.3 \
r-vegan \ # 多样性分析 R 包
r-ggplot2 \ # 可视化
bioconductor-phyloseq \ # 微生物组分析
&& micromamba clean --all --yes
ENV PATH="/opt/conda/bin:$PATH"
WORKDIR /workspace
CMD ["bash"]
# 构建并推送
docker build -t your_username/r-diversity:v1.0 -f Dockerfile.custom_analysis . # 构建
docker push your_username/r-diversity:v1.0 # 推送到 Docker Hub
5.2 在 Nextflow 中引用自定义镜像¶
// main.nf 中使用自定义镜像
process DIVERSITY_ANALYSIS {
tag "${sample_id}"
publishDir "${params.outdir}/04_diversity", mode: 'copy'
container 'your_username/r-diversity:v1.0' // 使用你自己构建的镜像
input:
tuple val(sample_id), path(kraken_report) // Kraken2 的分类报告
output:
path("${sample_id}_diversity.pdf"), emit: plot // 多样性图
path("${sample_id}_diversity.csv"), emit: table // 多样性指标表
script:
"""
Rscript /workspace/diversity_analysis.R \
--input ${kraken_report} \
--output_plot ${sample_id}_diversity.pdf \
--output_table ${sample_id}_diversity.csv
"""
}
5.3 本地镜像 + Nextflow(不推送到 Docker Hub)¶
如果你不想推送到公网,可以直接使用本地构建的镜像:
// nextflow.config
docker {
enabled = true
runOptions = '-u $(id -u):$(id -g)'
}
// main.nf 中的 container 直接写本地镜像名
process MY_TOOL {
container 'metagenome-pipeline:v1.0' // 本地镜像名(只要 docker images 能找到就行)
// ...
}
六、调试技巧¶
6.1 进入容器手动运行命令¶
当 Nextflow 某个 process 失败时,最有效的调试方式是进入容器手动运行:
# 第 1 步:找到失败 process 的工作目录
# Nextflow 会在终端输出类似这样的路径:
# [a1/b2c3d4] process > FASTP (sample01) [100%] 1 of 1, failed: 1
# work/a1/b2c3d4e5f6... 就是工作目录
# 第 2 步:进入工作目录查看
ls -la work/a1/b2c3d4*/ # 查看工作目录下的文件
cat work/a1/b2c3d4*/.command.sh # 查看 Nextflow 生成的实际运行脚本
cat work/a1/b2c3d4*/.command.err # 查看错误输出
cat work/a1/b2c3d4*/.command.log # 查看标准输出
# 第 3 步:进入容器手动调试
docker run -it --rm \
-v $(pwd)/work/a1/b2c3d4*:/workspace \ # 挂载失败的工作目录
quay.io/biocontainers/fastp:1.3.3--h5f740d0_0 \
bash
# 进入后手动执行 .command.sh 里的命令,看看到底哪里出错
6.2 查看 .nextflow/ 目录下的工作目录¶
# Nextflow 的工作目录结构
work/
├── a1/
│ └── b2c3d4e5f6.../ # 每个 task 的工作目录
│ ├── .command.sh # Nextflow 生成的执行脚本
│ ├── .command.run # 实际运行的包装脚本
│ ├── .command.err # 标准错误输出
│ ├── .command.out # 标准输出
│ ├── .command.log # 日志
│ ├── .command.begin # 开始标记文件
│ ├── .exitcode # 退出码(0 = 成功)
│ ├── sample_R1.fq.gz -> ../../input/... # 输入文件(符号链接)
│ └── sample_R1_clean.fq.gz # 输出文件
# 常用调试命令
cat work/a1/b2c3d4*/.exitcode # 查看退出码(0=成功,非0=失败)
cat work/a1/b2c3d4*/.command.err # 查看错误信息(最重要!)
ls -la work/a1/b2c3d4*/ # 查看文件列表(输入是否正确链接)
6.3 -with-docker 和 -with-singularity 参数¶
# 即使 nextflow.config 没配置 Docker,也可以临时启用
nextflow run main.nf -with-docker
# Nextflow 会自动用 Docker 运行所有有 container 指令的 process
# 指定统一的容器镜像
nextflow run main.nf -with-docker metagenome-pipeline:v1.0
# 所有 process 都用这一个镜像(覆盖 process 里的 container 设置)
# 使用 Singularity 运行
nextflow run main.nf -with-singularity
# 自动将 Docker 镜像转为 Singularity 格式运行
# 断点续跑(和容器配合完美)
nextflow run main.nf -resume
# -resume:只重跑失败或有变化的 process,已成功的跳过
6.4 容器缓存管理¶
# Docker 镜像缓存
docker images # 查看本地缓存的镜像
docker system df # 查看 Docker 占用的总磁盘空间
docker image prune # 删除无标签的悬空镜像
docker system prune -a # 清理所有未使用资源(谨慎!)
# Singularity 镜像缓存
ls ~/.singularity/cache/ # 查看缓存的 .sif 文件
du -sh ~/.singularity/cache/ # 查看缓存大小
# 删除缓存后 Nextflow 会重新下载转换,不影响结果
七、常见报错与解决方案¶
报错 1:容器内文件权限问题¶
原因:Docker 容器默认以 root 运行,但 Nextflow 工作目录属于当前用户。
// 解决方案:在 nextflow.config 中加 runOptions
docker {
enabled = true
runOptions = '-u $(id -u):$(id -g)' // 以当前用户身份运行容器
}
报错 2:Nextflow 找不到容器¶
原因:镜像 tag 不完整(缺少构建编号),或网络问题。
# 解决方案 1:使用完整的 tag(含构建编号)
# 错误:container 'quay.io/biocontainers/fastp:1.3.3'
# 正确:container 'quay.io/biocontainers/fastp:1.3.3--h5f740d0_0'
# 解决方案 2:预先拉取镜像
docker pull quay.io/biocontainers/fastp:1.3.3--h5f740d0_0 # 手动拉取
nextflow run main.nf # 再运行 Nextflow
# 解决方案 3:查看 Quay.io 上可用的 tag
# 访问 https://quay.io/repository/biocontainers/fastp?tab=tags
报错 3:Singularity 转换失败¶
原因:HPC 节点没有网络访问权限,无法从 Docker Hub/Quay.io 拉取镜像。
# 解决方案:在登录节点(有网络)预先拉取
# 第 1 步:在登录节点转换镜像
singularity pull --dir ~/.singularity/cache/ \
docker://quay.io/biocontainers/fastp:1.3.3--h5f740d0_0
# 这会在 cache 目录生成 .sif 文件
# 第 2 步:确认 nextflow.config 的 cacheDir 一致
singularity {
cacheDir = "$HOME/.singularity/cache" // 和拉取时的目录一致
}
# 第 3 步:提交作业时 Nextflow 会从缓存读取,不需要网络
nextflow run main.nf -profile cluster
报错 4:内存不足 (OOM Killed)¶
原因:退出码 137 表示被系统杀死(Out of Memory),Kraken2 数据库需要大量内存。
// 解决方案:增加内存分配
process {
withName: 'KRAKEN2_CLASSIFY' {
memory = '64 GB' // Kraken2 标准数据库需要约 40-50 GB 内存
}
}
八、速查表¶
Nextflow + Docker 配置速查¶
| 配置项 | 写在哪里 | 示例 |
|---|---|---|
| 启用 Docker | nextflow.config | docker.enabled = true |
| 设置容器用户 | nextflow.config | docker.runOptions = '-u $(id -u):$(id -g)' |
| process 级别容器 | main.nf 的 process 里 | container 'quay.io/biocontainers/fastp:1.3.3--h5f740d0_0' |
| 全局容器 | nextflow.config | process.container = 'image:tag' |
| 命令行临时启用 | 运行命令 | nextflow run main.nf -with-docker |
| 启用 Singularity | nextflow.config | singularity.enabled = true |
| Singularity 缓存 | nextflow.config | singularity.cacheDir = '~/.singularity/cache' |
| Singularity 自动挂载 | nextflow.config | singularity.autoMounts = true |
调试命令速查¶
| 场景 | 命令 |
|---|---|
| 查看失败日志 | cat work/xx/yyyy*/.command.err |
| 查看执行脚本 | cat work/xx/yyyy*/.command.sh |
| 查看退出码 | cat work/xx/yyyy*/.exitcode |
| 进入容器调试 | docker run -it --rm -v $(pwd)/work/xx/yyyy*:/ws 镜像名 bash |
| 断点续跑 | nextflow run main.nf -resume |
| 临时用 Docker | nextflow run main.nf -with-docker |
| 临时用 Singularity | nextflow run main.nf -with-singularity |
| 查看运行报告 | 浏览器打开 results/report.html |
| 清理工作目录 | nextflow clean -f (删除所有 work 目录) |
Docker vs Singularity 对比速查¶
| 特性 | Docker | Singularity/Apptainer |
|---|---|---|
| 需要 root 权限 | 是(或 docker 组) | 否 |
| 适用场景 | 本地开发、云服务器 | HPC 集群 |
| 镜像格式 | 分层 tar | 单个 .sif 文件 |
| Nextflow 配置 | docker.enabled = true | singularity.enabled = true |
| 命令行参数 | -with-docker | -with-singularity |
| 最新版本 | Docker Engine 29.4.2 | Apptainer 1.4.4 |
| 挂载语法 | -v 宿主机:容器 | --bind 宿主机:容器 |
版本参考(截至 2026 年 5 月)¶
| 软件 | 最新版本 |
|---|---|
| Docker Engine | 29.4.2 |
| Docker Compose | v5.1.3 |
| Apptainer(原 Singularity) | 1.4.4 |
| Nextflow(稳定版) | 25.04.6 |
| Nextflow(最新功能版) | 25.10.x |
| fastp | 1.3.3 |
| Bowtie2 | 2.5.5 |
| Kraken2 | 2.17.1 |
上一篇:564_Docker生信实战_从Dockerfile到镜像 系列完成:恭喜!你已经掌握了从 Dockerfile 构建到 Nextflow 容器化流程的完整技能链。接下来可以把这套方案直接应用到你的宏基因组分析项目中。