Nextflow workflow management

A practical tour of dataflow pipelines, anchored in a real nf-core production workflow: hlatyping.

MD Shakhaowat Hossain
PhD Candidate, Biomedical Sciences
Center for Biomedical Informatics and Genomics
Tulane University School of Medicine
HLA immunogenetics · transplant informatics
mhossain1@tulane.edu

Press S for speaker view · Space to advance · Esc for overview

Scope and attribution

This lecture is an educational walkthrough. Its goal is to build a clear conceptual understanding of how Nextflow, a widely adopted workflow management system, behaves in a real project setting rather than a toy example.

The worked example, nf-core/hlatyping, is an open-source, publicly available community pipeline. I use it here purely as a teaching artifact because it is production-grade and freely inspectable.

I am not a developer or maintainer of the hlatyping pipeline. All credit for its design and implementation belongs to the nf-core community and its contributors. Source: github.com/nf-core/hlatyping · nf-co.re/hlatyping.

Where we are going

  1. Why workflow managers exist (5 min)
  2. The Nextflow core model (8 min)
  3. DSL2 + channels (10 min)
  4. hlatyping walkthrough (8 min)
  5. Executors, containers, configuration (6 min)
  6. nf-core, resume, observability (4 min)
  7. Q&A (4 min)
The running example. nf-core/hlatyping performs 4-digit HLA class I typing from NGS reads using OptiType, with an optional HLA-HD path for class I + II. We will return to it on almost every slide.

Concrete > abstract. Every concept lands on a file you can grep.

The reproducibility tax on biological pipelines

  • A typical NGS pipeline is six to twelve tools, each with its own dependency graph, each with its own subtle defaults.
  • Two analysts on the same dataset routinely disagree at the third decimal place, sometimes at the variant call.
  • Reviewers ask: can you re-run this in six months with the same answer? Often the honest answer is no.
HLA-specific stake. A 4-digit class I typing call decides donor eligibility. A pipeline whose result drifts when an Ubuntu apt-mirror updates samtools is not a clinical-grade tool.

Bash pipelines hit a hard ceiling

What bash gives you

  • Pipes between two programs
  • A linear sequence of commands
  • Manual control of files and intermediates

What bash does not give you

  • Parallelism over samples
  • Restart from the failed step
  • Portability across local / HPC / cloud
  • Version-pinned environments
  • Provenance you can show a reviewer
# A typical sequencing pipeline, written as bash.
# Looks fine until sample 47 fails on a transient I/O blip.
for s in $(cat samples.txt); do
  fastqc reads/${s}_1.fq.gz reads/${s}_2.fq.gz -o qc/
  bwa mem -t 8 ref.fa reads/${s}_1.fq.gz reads/${s}_2.fq.gz \
    | samtools sort -@ 8 -o bam/${s}.bam -
  samtools index bam/${s}.bam
done
# Now: which sample is on which step? Which finished? Restart how?

The same task, as a Nextflow pipeline

// Identical work — main.nf. Run with: nextflow run main.nf -resume
process FASTQC {
    tag "$id"
    publishDir 'qc', mode: 'copy'

    input:
    tuple val(id), path(reads)

    output:
    path "*_fastqc.zip"

    script:
    "fastqc ${reads} -o ."
}

process BWA_MEM {
    tag "$id"
    cpus 8
    publishDir 'bam', mode: 'copy'

    input:
    tuple val(id), path(reads)
    path ref

    output:
    tuple val(id), path("${id}.bam"), path("${id}.bam.bai")

    script:
    """
    bwa mem -t ${task.cpus} ${ref} ${reads} \\
      | samtools sort -@ ${task.cpus} -o ${id}.bam -
    samtools index ${id}.bam
    """
}

workflow {
    reads_ch = Channel.fromFilePairs('reads/*_{1,2}.fq.gz')

    FASTQC(reads_ch)
    BWA_MEM(reads_ch, file('ref.fa'))
}

What the for-loop could not give you

  • Parallelism is free. fromFilePairs fans out; every sample runs FASTQC and BWA_MEM concurrently. No for loop, no manual job bookkeeping.
  • Resume from the failure. When sample 47 dies on an I/O blip, rerun with -resume: the 46 finished samples are cache hits, only 47 re-executes.
  • Portable, unchanged. -profile docker | singularity | awsbatch runs this same code on a laptop, an HPC queue, or the cloud.
  • Pinned and provenant. One container directive per process locks the tool version; every run emits execution_report.html and software_versions.yml.
Same three commands. The difference is everything around them.

The workflow manager landscape

ToolParadigmLangContainersCloud-nativeResume
MakeFile-target DAGMakeManualNoBy timestamp
SnakemakeFile-target DAGPythonYesAdd-onsYes
WDL / CromwellTask graphWDLYesYesYes
CWLTask graph (YAML)CWLYesEngine-dep.Engine-dep.
NextflowDataflow + channelsGroovy DSL2First-classFirst-class-resume

Snakemake and Nextflow dominate bioinformatics. The Broad uses WDL via Cromwell at planet-scale. We are teaching Nextflow today because nf-core, the community standard for curated pipelines, is built on it.

Part 2 · 8 minutes

The Nextflow core model

Dataflow programming, channels, processes, and the DAG that builds itself.

Dataflow programming, in one slide

You do not write the order things run in. You write what depends on what. Nextflow infers the DAG from that.

  • Process = a unit of work (a script + its inputs and outputs).
  • Channel = a typed, asynchronous queue of values.
  • Workflow = wiring channels between processes.
The DAG is implicit. If process B reads from process A's output channel, B waits for A. You never write a scheduler.
workflow {
    reads_ch = Channel.fromFilePairs('data/*_{1,2}.fq.gz')

    FASTQC(reads_ch)        // runs on every pair, in parallel
    BWA_MEM(reads_ch)       // also runs in parallel
    MULTIQC(FASTQC.out.zip.collect())   // waits for ALL FASTQCs
}

Sample-level parallelism is free. collect() is the only synchronization barrier in the example above.

Bash to DSL2, side by side

Click Morph to transform the bash pipeline on the left into the Nextflow equivalent on the right.

Bash

for s in $(cat samples.txt); do
  bwa mem ref.fa \
    reads/${s}_1.fq.gz reads/${s}_2.fq.gz \
    | samtools sort -o ${s}.bam -
  samtools index ${s}.bam
done

Nextflow DSL2

process BWA_MEM {
    tag "$meta.id"
    input:  tuple val(meta), path(reads)
    output: tuple val(meta), path("*.bam"), path("*.bai")

    script:
    """
    bwa mem ref.fa ${reads[0]} ${reads[1]} \
      | samtools sort -o ${meta.id}.bam -
    samtools index ${meta.id}.bam
    """
}

workflow {
    Channel.fromFilePairs('reads/*_{1,2}.fq.gz')
        | map { id, files -> [[id: id], files] }
        | BWA_MEM
}
Part 3 · 10 minutes

DSL2: the syntax that holds it all together

Processes, workflows, take/emit, modules, sub-workflows.

Anatomy of a process

process OPTITYPE {
    tag        "$meta.id"
    label      "process_medium"
    container  'quay.io/biocontainers/optitype:1.3.5--hdfd78af_3'
    publishDir "${params.outdir}/optitype", mode: 'copy'

    input:
    tuple val(meta), path(bam), path(bai)

    output:
    tuple val(meta), path("*_result.tsv"),        emit: hla_type
    tuple val(meta), path("*_coverage_plot.pdf"), emit: coverage_plot
    path "versions.yml",                          emit: versions

    script:
    """
    OptiTypePipeline.py -i ${bam} \\
        --${meta.seq_type} -v -o ./ -p ${meta.id}
    """
}

Directives (top): runtime metadata.

  • tag: human label per task
  • label: resource bucket in conf/base.config
  • container: pinned image, local and cloud
  • publishDir: where outputs land in results/

I/O contract: tuples, paths, val, named emit:s.

Script: bash by default; can be Python, R, or anything with a shebang.

workflow { take / emit }: composable sub-workflows

workflow NFCORE_HLATYPING {

    take:
    samplesheet  // channel: samplesheet read in from --input

    main:
    HLATYPING ( samplesheet )

    emit:
    multiqc_report = HLATYPING.out.multiqc_report
}

From main.nf in nf-core/hlatyping. take declares inputs; emit declares outputs. The body in main: is just like any workflow.

  • take: turns a workflow into a reusable function with named inputs.
  • emit: exposes named outputs that callers reference as WORKFLOW.out.name.
  • Without these, a workflow is the top-level entrypoint (the unnamed workflow {...}).
nf-core idiom. Every nf-core pipeline has at least three workflows: the top-level entrypoint, a PIPELINE_INITIALISATION sub-workflow that handles param parsing, and the named scientific workflow (HLATYPING).

Modules and sub-workflows: include

// workflows/hlatyping.nf (real, abridged)

include { CHECK_PAIRED          } from '../modules/local/check_paired'
include { HLAHD_INSTALL         } from '../modules/local/hlahd/install'
include { HLAHD                 } from '../modules/local/hlahd/genotype'

include { CAT_FASTQ             } from '../modules/nf-core/cat/fastq'
include { FASTQC                } from '../modules/nf-core/fastqc/main'
include { MULTIQC               } from '../modules/nf-core/multiqc/main'
include { GUNZIP                } from '../modules/nf-core/gunzip/main'
include { OPTITYPE              } from '../modules/nf-core/optitype/main'
include { SAMTOOLS_COLLATEFASTQ } from '../modules/nf-core/samtools/collatefastq/main'
include { SAMTOOLS_VIEW         } from '../modules/nf-core/samtools/view/main'
include { YARA_INDEX            } from '../modules/nf-core/yara/index/main'
include { YARA_MAPPER           } from '../modules/nf-core/yara/mapper/main'
  • modules/nf-core/ = community-curated, version-pinned, identical across pipelines.
  • modules/local/ = pipeline-specific (HLA-HD installer is license-gated, paired-end check for BAM input).
  • Includes are statically resolved. The DAG is known before any task runs.

The hlatyping main.nf entrypoint, in full

workflow {
    main:
    PIPELINE_INITIALISATION (
        params.version,        params.validate_params,
        params.monochrome_logs, args,
        params.outdir,         params.input,
        params.help,           params.help_full,
        params.show_hidden
    )

    NFCORE_HLATYPING ( PIPELINE_INITIALISATION.out.samplesheet )

    PIPELINE_COMPLETION (
        params.email,          params.email_on_fail,
        params.plaintext_email, params.outdir,
        params.monochrome_logs, params.hook_url,
        NFCORE_HLATYPING.out.multiqc_report
    )
}

Three sub-workflows, wired by named outputs. This is the entire entrypoint of a production pipeline. Everything else is composition.

Part 4 · 10 minutes

Channels are the program

Queue vs value, operators, and the patterns hlatyping actually uses.

Queue channels vs value channels

Queue channel

  • Asynchronous stream of N items.
  • Consumed once; emits to downstream processes as items arrive.
  • Natural fit for "one item per sample".
Channel.fromFilePairs('reads/*_{1,2}.fq.gz')
// emits one tuple per sample pair

Value channel

  • Holds a single value that can be read by any downstream process arbitrarily many times.
  • Natural fit for shared resources (reference genome, index, panel).
ch_ref = Channel.value(
    file("$projectDir/data/references/grch38.fa")
)
// every YARA_MAPPER task sees the same reference
The most common Nextflow bug. Two queue channels of unequal length combined naively produce zero outputs. Either pair them with .combine() / .cross() or convert one to a value channel.

Channel Cardinality & The collect() Barrier

Mismatched Cardinalities

  • When joining or combining two queue channels, Nextflow pairs them sequentially.
  • If ch_reads has 5 items and ch_index has 1 item, a process reading both runs only once.
  • A value channel (or a singleton channel combined) will implicitly broadcast to all.
Rule of thumb. Keep your sample metadata map (meta) attached to trace elements and ensure sizes align.

The Synchronization Barrier

  • The .collect() operator gathers all elements from a queue channel and emits them as a single list (value channel).
  • This forces a synchronization barrier: downstream processes wait for all upstream runs to finish.
// MultiQC runs once, waiting for all FastQC zips
MULTIQC ( FASTQC.out.zip.collect() )

Operators: a working catalog

OperatorWhat it doesUsed in hlatyping for
mapTransform each itemAttach the right HLA reference per seq_type
branchRoute items into named outputsSplit samplesheet into bam / fastq_multiple / fastq_single
crossPair items by their first elementPair each sample's reads with its Yara index
multiMapEmit into multiple named outputs from one inputSplit a cross result into reads and index streams
combineCartesian productBroadcast HLA-HD install artifact to every FASTQ
groupTupleGroup by keyGroup versions by process name before MultiQC
collectGather all items into a single listWait for all FastQC zips before MultiQC
mixMerge streams of the same shapeConcatenate version-yaml outputs from every process
joinInner join on first elementPair Yara BAM with its BAI before OptiType

Channel operator playground

Click an operator to see a marble diagram drawn from hlatyping's actual usage.

Marbles animate on each operator switch; replay to re-run.

branch in production: input-type routing

// workflows/hlatyping.nf — verbatim
ch_samplesheet
    .branch { meta, files ->
        bam : files[0].getExtension() == "bam"
        fastq_multiple :
            (meta.single_end && files.size() > 1) ||
            (!meta.single_end && files.size() > 2)
        fastq_single : true
    }
    .set { ch_input_files }

// Downstream: ch_input_files.bam, ch_input_files.fastq_multiple, ch_input_files.fastq_single
  • One channel in, three named channels out, by case.
  • The true on the last branch makes it the catch-all default.
  • Each branch can feed a different sub-DAG (BAM-to-FASTQ conversion vs cat vs straight FastQC).

cross + multiMap: pairing reads with their index

// workflows/hlatyping.nf — verbatim
ch_all_fastq                      // [ meta, [ reads ] ]
    .cross(YARA_INDEX.out.index)  // [ [meta, [reads]], [meta, index] ]
    .multiMap { reads, index ->   // splits joined cross tuple
        reads: reads              // emitted as: [ meta, [ reads ] ]
        index: index              // emitted as: [ meta, index ]
    }
    .set { ch_mapping_input }

YARA_MAPPER(
    ch_mapping_input.reads,
    ch_mapping_input.index
)

Data Shapes Step-by-Step

  1. ch_all_fastq: Emits [meta, [fastq_1, fastq_2]] per sample.
  2. YARA_INDEX.out.index: Emits [meta, index_dir].
  3. cross(): Aligns the elements by their first item (the meta map key). It outputs a nested tuple: [ [meta, [reads]], [meta, index] ].
  4. multiMap(): Deconstructs the pair and routes them into separate channels, ensuring the reads and index streams flow to YARA_MAPPER in exact lockstep.
Sample Safety. Without this coordination, Yara could align Patient A's reads to Patient B's custom reference.
Part 5 · 8 minutes

The hlatyping pipeline, end to end

A 4-digit class I caller you can put into a transplant-matching audit.

What we are typing, and from what

HLA in 30 seconds

  • HLA = MHC in humans. Class I (HLA-A, -B, -C) presents intracellular peptides.
  • Hyperpolymorphic: thousands of alleles per locus.
  • Standard reporting is 4-digit (e.g. A*02:01), protein-level resolution.
  • Used in transplant matching, neoantigen prediction, GWAS, pharmacogenomics.

Samplesheet (the only input contract)

sample,fastq_1,fastq_2,bam,seq_type
CONTROL_REP1,reads/c1_R1.fq.gz,reads/c1_R2.fq.gz,,dna
PATIENT_07,reads/p7_R1.fq.gz,reads/p7_R2.fq.gz,,rna
DONOR_A,,,reads/dA.bam,dna

Column rules: paired-end fills fastq_1 and fastq_2; BAM input goes in its own bam column with the FASTQ columns left empty. Only sample and seq_type are required; seq_type drives reference selection.

The pipeline, in six steps

  1. Merge FASTQs from the same sample (multi-lane), via cat.
  2. QC raw reads with FastQC.
  3. Index the curated HLA reference (DNA or RNA, per sample) with Yara.
  4. Pre-map reads to the HLA reference with Yara, producing a small BAM. Avoids OptiType's slower internal RazerS mapping.
  5. Type with OptiType: integer linear programming over the mapped reads, considering all class-I loci simultaneously.
  6. Aggregate with MultiQC: one HTML for QC + per-sample typing summary.
Optional HLA-HD branch. Set --tools optitype,hlahd with a valid --hlahd_path and the pipeline additionally produces class I + II calls at up to 6-digit (3-field) resolution.

The hlatyping DAG, one step at a time

Press Next to walk the DAG. The right column shows the channel contents at that stage.

Stage 1 / 8

Reading the source: reference selection + Yara

// workflows/hlatyping.nf — verbatim, abridged
if ( "optitype" in tools.tokenize(",") ) {
    ch_all_fastq
        .map { meta, reads ->
            [ meta, file("$projectDir/data/references/hla_reference_${meta['seq_type']}.fasta") ]
        }
        .set { ch_input_with_references }
    YARA_INDEX ( ch_input_with_references )
    ch_all_fastq
        .cross(YARA_INDEX.out.index)
        .multiMap { reads, index ->
            reads: reads
            index: index
        }
        .set { ch_mapping_input }
    YARA_MAPPER ( ch_mapping_input.reads, ch_mapping_input.index )
    OPTITYPE ( YARA_MAPPER.out.bam.join(YARA_MAPPER.out.bai) )
}

Note how seq_type (dna vs rna) selects a different reference FASTA, automatically, per sample.

Why two tools, not one aligner? The classical HLA loci share deep sequence homology with each other and with non-expressed pseudogenes (HLA-H, -J, -K, …). A naive aligner spreads a read across several near-identical loci, so a per-locus best-hit call is ambiguous by construction. YARA_MAPPER first constrains reads to the curated HLA reference (an all-mappings, not best-hit, alignment), then OptiType resolves the ambiguity globally: it formulates allele selection as an integer linear program over all class-I loci at once, choosing the genotype that best explains the full read set rather than locus by locus. That global step is what gets you transplant-grade 4-digit calls.

Provenance: Channel.topic("versions")

// workflows/hlatyping.nf — verbatim
def topic_versions = Channel.topic("versions")
    .distinct()
    .branch { entry ->
        versions_file: entry instanceof Path
        versions_tuple: true
    }

def topic_versions_string = topic_versions.versions_tuple
    .map { process, tool, version ->
        [ process[process.lastIndexOf(':')+1..-1], "  ${tool}: ${version}" ]
    }
    .groupTuple(by: 0)
    .map { process, tool_versions ->
        tool_versions.unique().sort()
        "${process}:\n${tool_versions.join('\n')}"
    }
  • Topic channels let any process emit into a named global stream (here, "versions") without explicit wiring.
  • Combined with groupTuple, the pipeline ends with a software_versions.yml that lists every tool and version it ran.
  • That YAML is bundled into the MultiQC report. Reviewer-ready provenance, for free.

Conditional sub-DAGs: opting into HLA-HD

if ( "hlahd" in tools.tokenize(",") ) {
    def hlahd_software_meta = file("$projectDir/assets/hlahd_software_meta.json",
                                   checkIfExists: true)
    def jsonSlurper = new groovy.json.JsonSlurper()
    def hlahd_meta = jsonSlurper.parse(hlahd_software_meta)['hlahd']

    def ch_hlahd_install = Channel.of([
        'hlahd', hlahd_meta.version, hlahd_meta.software_md5,
        file(params.hlahd_path, checkIfExists: true),
        params.hlahd_update_reference_dict,
    ])

    HLAHD_INSTALL(ch_hlahd_install)
    HLAHD(ch_all_fastq.combine(HLAHD_INSTALL.out.hlahd))
    ch_versions = ch_versions.mix(HLAHD.out.versions)
}
  • HLA-HD is license-gated, so it is opt-in via --tools optitype,hlahd.
  • combine broadcasts the single install artifact to every sample (Cartesian product on a size-1 channel).
  • ch_versions.mix(...) appends HLA-HD's versions to the same provenance stream as everything else.

Try it: edit a process body

Change ${params.outdir}, the OptiType flags, or meta.seq_type on the left. The right pane shows how Nextflow would render the script block at runtime. Switch samplesheet rows to watch the same process body resolve differently per sample.

Param substitution preview only. No execution — that is the runtime's job.

Part 6 · 6 minutes

Portability: one pipeline, every backend

Executors, containers, and the configuration layer that abstracts them.

Same pipeline, four backends. Only the config changes.

// Filled by app.js

The process definitions and workflow wiring never change. Only nextflow.config swaps. That is the entire portability story.

Containers: one per process, version-pinned

  • Docker on workstations and managed cloud.
  • Singularity / Apptainer on HPC (no root needed; works with shared filesystems).
  • Conda as a last-resort fallback (slower, less reproducible).
  • Each process declares its own container. No global runtime to maintain.
nf-core convention: every module pins a Biocontainers image with a specific tool version and build hash. Two runs three months apart, same container hash, bit-identical output.
process OPTITYPE {
    container 'quay.io/biocontainers/optitype:1.3.5--hdfd78af_3'
    // ... rest of the process
}

// Or, with separate Docker/Singularity URIs:
process YARA_MAPPER {
    container "${ workflow.containerEngine == 'singularity' &&
        !task.ext.singularity_pull_docker_container ?
        'https://depot.galaxyproject.org/singularity/yara:1.0.2--he1c1bb9_4' :
        'quay.io/biocontainers/yara:1.0.2--he1c1bb9_4' }"
}

nextflow.config and profiles

// conf/base.config (real hlatyping pattern)
process {
    cpus   = { 1   * task.attempt }
    memory = { 6.GB * task.attempt }
    time   = { 4.h * task.attempt }

    errorStrategy = { task.exitStatus in [137,143] ? 'retry' : 'finish' }
    maxRetries    = 2

    withLabel: process_medium {
        cpus   = { 6    * task.attempt }
        memory = { 36.GB * task.attempt }
    }
    withName: OPTITYPE {
        memory = { 16.GB * task.attempt }
    }
}

Profiles

  • -profile docker — local Docker
  • -profile singularity — HPC
  • -profile conda — Conda fallback
  • -profile test — tiny test data + Docker
  • -profile test_full — full-size CI

hlatyping ships eight test profiles

test, test_fastq, test_fastq_cat, test_rna, test_dna_rna, test_hlahd, test_optitype_hlahd, test_full — each exercising a different input pattern.

Part 7 · 4 minutes

nf-core, resume, and the reports that justify a run

The community standard, the runtime trick that pays for itself in an afternoon, and the audit trail.

nf-core: shared modules, shared standards

What nf-core gives you

  • ~100 curated end-to-end pipelines (rnaseq, sarek, ampliseq, hlatyping, …).
  • ~1500 versioned, tested process modules in modules/nf-core/.
  • A standard project template (the one hlatyping uses, v3.5.1).
  • CI (nf-test, GitHub Actions, full-size AWS runs).
  • The launch UI on Seqera Platform, driven by nextflow_schema.json.
Read once, run anywhere. Every nf-core pipeline you encounter has the same layout, the same flags, the same provenance pattern.

The local-vs-nf-core split, in hlatyping

modules/
├── local/
│   ├── check_paired/          # is this BAM paired-end?
│   ├── hlahd/install/         # license-gated install
│   └── hlahd/genotype/        # license-gated typing
└── nf-core/
    ├── cat/fastq/
    ├── fastqc/
    ├── gunzip/
    ├── multiqc/
    ├── optitype/
    ├── samtools/collatefastq/
    ├── samtools/view/
    ├── yara/index/
    └── yara/mapper/

If a future pipeline needs Yara, it imports the same module. No copy-paste drift.

Tooling: nf-core/tools, nf-test, and the schema-driven UI

nf-core/tools

  • nf-core create
  • nf-core lint
  • nf-core sync
  • nf-core modules install

CLI for spinning up, updating, and lint-checking nf-core pipelines.

nf-test

  • Unit tests for processes
  • Snapshot tests for outputs
  • CI on every PR

hlatyping ships an nf-test.config and a tests/ tree. Changes that break a process do not merge.

nextflow_schema.json

  • Machine-readable parameter spec
  • Drives CLI --help output
  • Drives the Seqera launch form

One schema, three surfaces (CLI, docs, web UI). No drift between them.

-resume: the single most loved Nextflow feature

Nextflow hashes (inputs + script body + container digest + directives) per task. On -resume, every task with a matching hash is fetched from work/ instead of rerun.

Run 1 · cold

Run 2 · --tools optitype,hlahd -resume

Only the HLA-HD branch is new in run 2. FastQC, Yara, OptiType are cache-hit; MultiQC re-runs because its input set has changed.

Observability you get for free

Built-in HTML reports

  • pipeline_info/execution_report.html — CPU, memory, wall-clock per task.
  • pipeline_info/execution_timeline.html — Gantt chart.
  • pipeline_info/pipeline_dag.svg — the rendered DAG.
  • pipeline_info/software_versions.yml — provenance, every tool, every version.
  • multiqc/multiqc_report.html — domain-level QC + typing summary.

Seqera Platform (formerly Tower)

  • Web UI to launch, monitor, and audit Nextflow runs.
  • Real-time per-task metrics during a 200-sample run.
  • Launch-form auto-generated from nextflow_schema.json.
  • Free tier for personal / academic use.
Open pipeline_dag.svg after your first real run. It is the fastest way to internalize how dataflow composes processes.

The same reports, visualized

Left: execution_timeline.html for one sample — hover any task for its resource profile. Right: a live Seqera run, the same data streamed during execution.

OPTITYPE is the resource hotspot: modest CPU, but the ILP solve drives peak RSS. That single fact is why the SLURM profile gave it a withName: OPTITYPE memory override.

Why this matters: HLA, transplant, and immunogenomics

  • Transplant matching. 4-digit class I typing for HSCT and solid-organ donor selection. A version-pinned OptiType means the typing call from your January cohort and your June cohort are directly comparable.
  • Neoantigen prediction. NetMHCpan needs HLA alleles. hlatyping is upstream of every immuno-oncology pipeline that uses MHC class I binding affinity.
  • Pharmacogenomic screening. HLA-B*57:01 before abacavir; HLA-B*15:02 before carbamazepine. The same FASTQ-to-allele pipeline serves both.
  • Population HLA frequency studies. Run a 10 000-sample cohort with -profile awsbatch; the provenance YAML lands next to every result for downstream meta-analysis.
  • HLA-disease GWAS imputation validation. Sequence-derived typing as ground truth for SNP-based HLA imputation methods.

When not to use Nextflow

  • One-off scripts. Parse a CSV, plot a figure. Bash or a notebook is faster.
  • Heavily interactive analyses. Exploratory R/Python work, GUI-driven plotting. Nextflow's strength is batch reproducibility, not iteration speed.
  • Single-process tools. If your "pipeline" is one Python script, wrapping it in a single-process Nextflow workflow buys you containers and resume — useful, but ask whether the workflow scaffolding is overkill for one command.
  • No portability need. If the work will only ever run on your laptop, the abstraction tax is not worth it.
The honest pitch. Nextflow shines when you have multiple samples, multiple tools, and a future audit. It is a load-bearing investment, not a script wrapper.

Resources and citations

Start here

Papers

  • Di Tommaso et al. Nextflow enables reproducible computational workflows. Nat Biotechnol 35, 316–319 (2017).
  • Ewels et al. The nf-core framework for community-curated bioinformatics pipelines. Nat Biotechnol 38, 276–278 (2020).
  • Szolek et al. OptiType: precision HLA typing from next-generation sequencing data. Bioinformatics 30, 3310–3316 (2014).
  • Kawaguchi et al. HLA-HD: an accurate HLA typing algorithm for next-generation sequencing data. Hum Mutat 38, 788–797 (2017).
Thank you

Questions?

Slides and the worked-example references are linked from the deck.

MD Shakhaowat Hossain
PhD Candidate, Biomedical Sciences
Center for Biomedical Informatics and Genomics
Tulane University School of Medicine
HLA immunogenetics · transplant informatics
mhossain1@tulane.edu