跳转至

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(2015-2021)
    ↓ 2021 年改名
Apptainer(2021-至今,归属 Linux 基金会)
    当前最新版本:1.4.4
  • 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:容器内文件权限问题

ERROR: Permission denied: '/workspace/output/sample_clean.fq.gz'

原因:Docker 容器默认以 root 运行,但 Nextflow 工作目录属于当前用户。

// 解决方案:在 nextflow.config 中加 runOptions
docker {
    enabled    = true
    runOptions = '-u $(id -u):$(id -g)'  // 以当前用户身份运行容器
}

报错 2:Nextflow 找不到容器

ERROR ~ Cannot find container image 'quay.io/biocontainers/fastp:1.3.3'

原因:镜像 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 转换失败

FATAL: Unable to pull docker://quay.io/biocontainers/fastp:1.3.3--h5f740d0_0

原因: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)

Process KRAKEN2_CLASSIFY terminated with exit status 137

原因:退出码 137 表示被系统杀死(Out of Memory),Kraken2 数据库需要大量内存。

// 解决方案:增加内存分配
process {
    withName: 'KRAKEN2_CLASSIFY' {
        memory = '64 GB'  // Kraken2 标准数据库需要约 40-50 GB 内存
    }
}

八、速查表

Nextflow + Docker 配置速查

配置项写在哪里示例
启用 Dockernextflow.configdocker.enabled = true
设置容器用户nextflow.configdocker.runOptions = '-u $(id -u):$(id -g)'
process 级别容器main.nf 的 process 里container 'quay.io/biocontainers/fastp:1.3.3--h5f740d0_0'
全局容器nextflow.configprocess.container = 'image:tag'
命令行临时启用运行命令nextflow run main.nf -with-docker
启用 Singularitynextflow.configsingularity.enabled = true
Singularity 缓存nextflow.configsingularity.cacheDir = '~/.singularity/cache'
Singularity 自动挂载nextflow.configsingularity.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
临时用 Dockernextflow run main.nf -with-docker
临时用 Singularitynextflow run main.nf -with-singularity
查看运行报告浏览器打开 results/report.html
清理工作目录nextflow clean -f (删除所有 work 目录)

Docker vs Singularity 对比速查

特性DockerSingularity/Apptainer
需要 root 权限是(或 docker 组)
适用场景本地开发、云服务器HPC 集群
镜像格式分层 tar单个 .sif 文件
Nextflow 配置docker.enabled = truesingularity.enabled = true
命令行参数-with-docker-with-singularity
最新版本Docker Engine 29.4.2Apptainer 1.4.4
挂载语法-v 宿主机:容器--bind 宿主机:容器

版本参考(截至 2026 年 5 月)

软件最新版本
Docker Engine29.4.2
Docker Composev5.1.3
Apptainer(原 Singularity)1.4.4
Nextflow(稳定版)25.04.6
Nextflow(最新功能版)25.10.x
fastp1.3.3
Bowtie22.5.5
Kraken22.17.1

上一篇564_Docker生信实战_从Dockerfile到镜像 系列完成:恭喜!你已经掌握了从 Dockerfile 构建到 Nextflow 容器化流程的完整技能链。接下来可以把这套方案直接应用到你的宏基因组分析项目中。