Skip to content

Nextflow

Nextflow is a workflow manager that submits each step of your pipeline as a separate SLURM job. You define the steps and their data dependencies; Nextflow handles job submission, parallelism, staging files, and retrying failed tasks. If a pipeline fails halfway, you can resume from where it left off without re-running completed steps.

For a general introduction to the Nextflow language (processes, channels, operators), see the official documentation. This page focuses on running Nextflow on our clusters.

Loading Nextflow

module load Nextflow/25.04.6

Java is loaded automatically as a dependency. Quick test to verify the installation:

hello.nf
process GREET {
    input:
    val name

    output:
    stdout

    script:
    """
    echo "Hello, ${name}!"
    """
}

workflow {
    names = Channel.of("Alice", "Bob", "Charlie")
    GREET(names) | view
}
nextflow run hello.nf
 N E X T F L O W   ~  version 25.04.6

Launching `hello.nf` [hopeful_panini] DSL2 - revision: 9a7ae29534

executor >  local (3)
[6c/f5f811] GREET (3) [100%] 3 of 3 ✔
Hello, Alice!

Hello, Bob!

Hello, Charlie!
Available versions on each cluster
Nextflow/23.04.2 Nextflow/24.04.2 Nextflow/25.04.6
Nextflow/22.10.1

Not available on this cluster.

SLURM configuration

Nextflow needs a nextflow.config file in your project directory to know how to submit jobs. At minimum:

nextflow.config
process {
    executor = "slurm"
    queue    = "general"
    cpus     = 4
    memory   = "8 GB"
    time     = "1d"
}

With this configuration, every task in your pipeline becomes a SLURM job submitted via sbatch. Nextflow monitors their state and launches downstream tasks as dependencies are satisfied.

Partitions and QoS

In Nextflow, the queue directive maps to the SLURM partition (--partition). To set the QoS, use clusterOptions:

process {
    queue          = "general"
    clusterOptions = "--qos=long"
}
See Job submission (SLURM) for available partitions and QoS limits on each cluster.

Nextflow needs a persistent session

Nextflow must keep running for the entire duration of the pipeline because it monitors jobs and submits new ones as dependencies are satisfied. Run it inside a screen or tmux session so it survives disconnections, or submit the Nextflow launcher itself as a SLURM job.

Work directory

By default, Nextflow creates a work/ directory in the current folder. On the cluster, point it to /scratch for better performance and to avoid filling your home quota:

nextflow run pipeline.nf -w /scratch/<username>/nf-work

Or set it permanently in nextflow.config:

workDir = "/scratch/<username>/nf-work"

Each task gets a subdirectory inside work/ containing the script (.command.sh), logs (.command.log), and output files. These are essential for debugging.

Per-process resources

Different steps usually need different resources. Use labels in your pipeline and configure them in nextflow.config:

nextflow.config
process {
    executor = "slurm"
    queue    = "general"

    withLabel: small {
        cpus   = 2
        memory = "4 GB"
        time   = "1h"
    }

    withLabel: big {
        cpus   = 16
        memory = "64 GB"
        time   = "12h"
    }
}

You can also target a specific process by name:

process {
    withName: ALIGN {
        cpus           = 16
        memory         = "64 GB"
        time           = "2d"
        clusterOptions = "--qos=long"
    }
}

Dynamic resources with automatic retry

Tasks that run out of memory are killed by SLURM (exit code 137). A common pattern is to increase resources on each retry:

nextflow.config
process {
    executor      = "slurm"
    errorStrategy = { task.exitStatus == 137 ? "retry" : "terminate" }
    maxRetries    = 3

    memory = { 8.GB * task.attempt }
    time   = { 2.h  * task.attempt }
}

First attempt: 8 GB / 2 h. Second: 16 GB / 4 h. Third: 24 GB / 6 h.

GPU jobs

Use clusterOptions to request GPUs. On Hyperion, use --constraint to select a specific GPU type:

nextflow.config
process {
    withName: INFERENCE {
        queue          = "general"
        clusterOptions = "--gres=gpu:1 --constraint=a100-pcie"
        cpus           = 8
        memory         = "32 GB"
        time           = "1d"
    }
}

Available constraints: a100-sxm4, a100-pcie, rtx3090, a6000.

For small GPU jobs (1-3 GPUs), the preemption-gpu partition gives access to A100 SXM4 nodes without the 4-GPU minimum of the general partition:

process {
    withName: SMALL_GPU_TASK {
        queue          = "preemption-gpu"
        clusterOptions = "--gres=gpu:1 --qos=preemption-gpu"
        cpus           = 4
        memory         = "32 GB"
        time           = "1d"
    }
}

Note

Jobs in preemption-gpu can be preempted (cancelled) if the node owner submits a job. Use checkpointing for long-running GPU tasks on this partition.

nextflow.config
process {
    withName: INFERENCE {
        queue          = "general"
        clusterOptions = "--gres=gpu:rtx3090:1"
        cpus           = 8
        memory         = "32 GB"
        time           = "1d"
    }
}

Available GPUs: rtx3090 (4 nodes, 2 GPUs each), p40 (32 nodes, 1-2 GPUs each).

Profiles

Profiles let you switch between clusters without changing the pipeline:

nextflow.config
profiles {
    hyperion {
        process.executor = "slurm"
        process.queue    = "general"
        workDir          = "/scratch/<username>/nf-work"
    }

    atlas {
        process.executor = "slurm"
        process.queue    = "general"
        workDir          = "/scratch/<username>/nf-work"
    }

    local {
        process.executor = "local"
        process.cpus     = 4
    }
}

Select a profile at runtime:

nextflow run pipeline.nf -profile hyperion

Tip

For pipelines with many short tasks, regular QoS works well. For pipelines with few long tasks, use --qos=long or --qos=xlong. See Job submission (SLURM) for the full QoS reference on each cluster.

Using containers

Nextflow integrates with Apptainer natively. When you specify a container, each task runs inside it automatically.

Configuration

Apptainer must be loaded both in your session (Nextflow pulls images from the login node) and inside each SLURM task:

module load Apptainer
nextflow.config
apptainer.enabled    = true
apptainer.autoMounts = true
apptainer.runOptions = "--nv"
apptainer.cacheDir   = "/scratch/<username>/.apptainer-cache"

process {
    beforeScript = "module load Apptainer"
}

The --nv flag exposes the host GPU drivers inside the container, which is required for any GPU workload. It is harmless on CPU-only tasks.

Cache on shared storage

apptainer.cacheDir must be on /scratch so compute nodes can access the images. If you skip this, each node re-downloads the image.

Specifying containers

Per process in nextflow.config:

process {
    withName: ALIGN {
        container = "docker://biocontainers/bwa:v0.7.17"
    }
    withName: QUANTIFY {
        container = "/scratch/<username>/containers/salmon.sif"
    }
}

When you use a docker:// URI, Nextflow pulls the image and converts it to SIF format automatically. Pre-built .sif files can be referenced directly by path.

Complete example: SLURM + Apptainer on Hyperion

A minimal pipeline that runs one CPU task and one GPU task, each inside its own container:

demo.nf
process CPU_TASK {
    label "cpu"

    output:
    stdout

    script:
    """
    echo "Running on \$(hostname) with \$(nproc) CPUs"
    cat /etc/os-release | grep PRETTY_NAME
    """
}

process GPU_TASK {
    label "gpu"

    output:
    stdout

    script:
    """
    echo "Running on \$(hostname)"
    nvidia-smi --query-gpu=name,memory.total --format=csv,noheader
    """
}

workflow {
    CPU_TASK() | view
    GPU_TASK() | view
}
nextflow.config
apptainer.enabled    = true
apptainer.autoMounts = true
apptainer.runOptions = "--nv"
apptainer.cacheDir   = "/scratch/<username>/.apptainer-cache"
workDir              = "/scratch/<username>/nf-work"

process {
    executor     = "slurm"
    queue        = "general"
    beforeScript = "module load Apptainer"

    withLabel: cpu {
        cpus      = 4
        memory    = "8 GB"
        time      = "2h"
        container = "docker://ubuntu:24.04"
    }

    withLabel: gpu {
        cpus           = 8
        memory         = "32 GB"
        time           = "6h"
        clusterOptions = "--gres=gpu:1 --constraint=a100-pcie"
        container      = "docker://nvidia/cuda:12.4.1-base-ubuntu22.04"
    }
}

Place both files in the same directory and run the pipeline:

module load Nextflow/25.04.6 Apptainer
nextflow run demo.nf

Nextflow submits two SLURM jobs (one per task), pulls the container images on first run, and prints the results as each task completes:

 N E X T F L O W   ~  version 25.04.6

Launching `demo.nf` [admiring_stone] DSL2 - revision: 8ae07c8af1

executor >  slurm (2)
[62/8337c2] CPU_TASK [100%] 1 of 1 ✔
[59/98990a] GPU_TASK [100%] 1 of 1 ✔
Running on hyperion-257
NVIDIA A100 80GB PCIe, 81920 MiB

Completed at: 16-Mar-2026 14:41:52
Duration    : 48m 55s
CPU hours   : (a few seconds)
Succeeded   : 2

The GPU task ran on hyperion-257 and correctly detected the A100 PCIe GPU inside the container.

Running nf-core pipelines

nf-core provides production-ready pipelines for common bioinformatics analyses. These pipelines include both singularity and apptainer profiles that handle container downloads automatically.

Set the cache directory before running:

export NXF_APPTAINER_CACHEDIR=/scratch/<username>/.apptainer-cache
mkdir -p $NXF_APPTAINER_CACHEDIR

Tip

Add the export line to your ~/.bashrc so it applies to every session.

Example with nf-core/rnaseq (see the nf-core/rnaseq usage docs for the samplesheet format and available options):

module load Nextflow/25.04.6 Apptainer

nextflow run nf-core/rnaseq \
    -r 3.14.0 \
    -profile apptainer \
    --input samplesheet.csv \
    --outdir results \
    --genome GRCh38 \
    -w /scratch/<username>/nf-work

Some popular nf-core pipelines:

Pipeline Purpose
nf-core/rnaseq RNA-seq quantification
nf-core/sarek Variant calling (WGS/WES)
nf-core/fetchngs Download from SRA/ENA/GEO
nf-core/mag Metagenome assembly
nf-core/chipseq ChIP-seq peak calling

Resuming failed pipelines

If a pipeline fails, fix the issue and re-run with -resume:

nextflow run pipeline.nf -resume

Nextflow reuses cached results from work/ and only re-executes failed or new tasks.

Monitoring

Generate an HTML report with resource usage per task:

nextflow run pipeline.nf -with-report report.html -with-timeline timeline.html

View past runs:

nextflow log

Cleaning up

The work/ directory grows quickly. After a successful run, clean it:

nextflow clean -f

Or remove only runs older than a specific one:

nextflow clean -f -before <run_name>

Troubleshooting

My pipeline failed. How do I debug it?
  1. Read the error message; Nextflow shows which process and task failed
  2. Go to the task's work directory (the path is in the error output)
  3. Check .command.log and .command.err for the actual error
  4. Check .command.sh to see the exact script that ran
  5. Fix the issue and run again with -resume
Jobs stay in PENDING state forever

The resource request likely exceeds what the partition allows. Check:

  • Is the queue (partition) correct? Both Hyperion and Atlas use general as default
  • Does the QoS allow the requested walltime? (regular allows 1 day max)
  • Is memory within the node's limit?

Run squeue -u <username> to see pending jobs and their reasons.

Container image not found on compute nodes

Make sure apptainer.cacheDir points to /scratch. All compute nodes share the /scratch filesystem.

Java not found

Java is loaded automatically with the Nextflow module. Make sure you loaded it:

module load Nextflow/25.04.6

SLURM rejects jobs with 'Invalid qos specification'

The QoS name depends on the cluster. Verify available QoS with:

sacctmgr show qos format=name,maxwall,maxtresperuser
Common issue: using long without --qos=long in clusterOptions (by default, the QoS is regular).