nf-core/configs: MCW RCC Configuration

The Medical College of Wisconsin Research Computing Center (RCC) runs a SLURM-managed HPC cluster (login hosts ln*, scratch on qfs1.rcc.mcw.edu:/scratchfs).

Important: keep everything on /scratch, not /group

The /scratch filesystem (qfs1.rcc.mcw.edu:/scratchfs) is mounted on both login nodes and compute nodes. The /group filesystem (qfs2.rcc.mcw.edu:/groupfs) is mounted on login nodes only — compute nodes cannot see it.

A Nextflow run reads its samplesheet, container cache, and reference files from compute nodes, not from the login node where you launched nextflow. Anything under /group will appear missing or empty to running tasks and the pipeline will fail in confusing ways.

Concretely, before launching a pipeline:

  • -work-dir must point under /scratch/g/<user>/...
  • --outdir must point under /scratch/g/<user>/...
  • --input (samplesheet) and any local reference files (FASTA, GTF, …) must live under /scratch/g/<user>/...

-profile mcw_rcc only binds /scratch and /hpc into containers, not /group, so this constraint is enforced for image-level access too.

To use, run the pipeline with -profile mcw_rcc. This loads mcw_rcc.config, which is pre-configured for this cluster’s SLURM partitions, per-core memory limits, and Apptainer container runtime.

Prerequisites

Load both Nextflow and Apptainer in your submission script:

module load nextflow/25.10.2
module load apptainer/1.4.1

Apptainer must be loaded in the driver script (not just inside tasks) because Nextflow pulls each container image from its own driver process before launching any task — at that point, no process.beforeScript has had a chance to run. The profile still adds module load apptainer/1.4.1 to every task’s beforeScript, so once the driver-side pull succeeds, individual tasks find apptainer correctly too.

Partitions and auto-routing

The profile picks a SLURM partition per task based on the task’s declared resources:

PartitionNodes × CPU × MemoryMax walltimeWhen this profile uses it
normal60 × 48 CPU × 360 GB7 daysDefault for any task at ≤ 7.5 GB / core and ≤ 360 GB / node
bigmem2 × 48 CPU × 1.5 TB7 daysAuto-selected when memory > 360 GB or memory / cpus > 7.5 GB
gpu11 mixed V100 / A40 / L40S7 daysTasks labeled process_gpu; one GPU is requested via --gres=gpu:1

The normal partition rejects allocations with more than 7680 MB per CPU. Rather than letting those submissions fail, the profile routes them to bigmem. If you find a process is being sent to bigmem more aggressively than you want, lower its memory request or raise its CPU request (e.g. via withName: '<PROC>' { memory = '...' } in a custom -c config).

Container images and caching

-profile mcw_rcc enables Apptainer with autoMounts = true and binds /scratch and /hpc into every container. Apptainer 1.x is fully compatible with images built for Singularity, so nf-core images pull and run unchanged.

You must point two apptainer cache variables at /scratch before launching Nextflow. Home directories on this cluster are ~94 GB and typically near-full; the OCI → SIF conversion that apptainer performs during a fresh image pull writes multi-gigabyte temp blobs and will exhaust home in a single run if not redirected.

# Apptainer's own blob cache and temp dir (used during pull/build):
export APPTAINER_CACHEDIR=/scratch/g/$USER/.apptainer/cache
export APPTAINER_TMPDIR=/scratch/g/$USER/.apptainer/tmp
 
# Create them once — apptainer fails if APPTAINER_TMPDIR doesn't exist:
mkdir -p "$APPTAINER_CACHEDIR" "$APPTAINER_TMPDIR"

The profile already sets apptainer.cacheDir (the persistent .img store, equivalent to NXF_APPTAINER_CACHEDIR) to /scratch/g/$USER/nf-apptainer-cache, so you do not need to export that one. The two variables above must still be exported in your submission script, because the driver-side pull happens before Nextflow’s env { } scope takes effect.

Running on a compute node, not the login node

nextflow itself coordinates the run and must stay alive for hours to days. Submit it as a SLURM job rather than running it on a login node. A minimal submission script:

#!/bin/bash
#SBATCH --job-name=nf-driver
#SBATCH --partition=normal
#SBATCH --cpus-per-task=2
#SBATCH --mem=8G
#SBATCH --time=72:00:00
 
module load nextflow/25.10.2
module load apptainer/1.4.1
 
export APPTAINER_CACHEDIR=/scratch/g/$USER/.apptainer/cache
export APPTAINER_TMPDIR=/scratch/g/$USER/.apptainer/tmp
mkdir -p "$APPTAINER_CACHEDIR" "$APPTAINER_TMPDIR"
 
nextflow run nf-core/<pipeline> \
    -profile mcw_rcc \
    --input /scratch/g/$USER/samplesheet.csv \
    --outdir /scratch/g/$USER/results \
    -work-dir /scratch/g/$USER/work

Keep -work-dir under /scratch (large, fast GPFS) rather than under your home directory.

Notes

  • The cluster previously offered a singularity module; it was replaced by apptainer/1.4.1. Existing scripts that ran with -profile singularity plus module load singularity should switch to -profile mcw_rcc.
  • GPU support is provided via the process_gpu label. Pipelines that do not declare GPU labels will not use GPU nodes.
  • Open-OnDemand sessions run on the ood partition (12 h max) and are not used by this profile for pipeline workloads.

Config file

See config file on GitHub

conf/mcw_rcc
// nf-core/configs: Medical College of Wisconsin (MCW) RCC profile
//
// Cluster: MCW Research Computing Center (RCC)
// Scheduler: SLURM
// Container engine: Apptainer 1.4.x (drop-in replacement for Singularity)
//
// Partition characteristics observed on this cluster:
//   normal (default) — 60 × (48 CPU / 360 GB / 7 d) — MaxMemPerCPU 7680 MB
//   bigmem           —  2 × (48 CPU / 1.5 TB / 7 d) — MaxMemPerCPU 31500 MB
//   gpu              — 11 nodes, mixed V100 / A40 / L40S, 7 d max
//
// Jobs that would exceed the normal partition's 7.5 GB-per-core cap, or
// its 360 GB-per-node cap, are routed to `bigmem` automatically. GPU
// jobs (label `process_gpu`) are routed to `gpu` and request one device.
//
// Filesystem note: `/scratch` is mounted on compute nodes; `/group` is
// NOT (login-node only). All inputs, outputs, work dirs, samplesheets,
// and the apptainer cache MUST live under `/scratch/g/<user>/` or they
// will appear empty to running tasks. The container bind list below
// intentionally omits `/group`.
 
params {
    config_profile_description    = 'Medical College of Wisconsin (MCW) Research Computing Center cluster profile provided by nf-core/configs.'
    config_profile_contact        = 'Nick Semenkovich'
    config_profile_contact_github = '@semenko'
    config_profile_contact_email  = 'nsemenkovich@mcw.edu'
    config_profile_url            = 'https://www.mcw.edu/departments/clinical-and-translational-science-institute/research-services/research-computing'
}
 
// NOTE: Apptainer is gated behind a module on this cluster, so users
// MUST `module load apptainer/1.4.1` (or any 1.4.x release) AND
// `export APPTAINER_CACHEDIR=/scratch/g/$USER/.apptainer/cache` (plus
// APPTAINER_TMPDIR) in their submission script before invoking
// `nextflow run`. The driver-side image pull and OCI→SIF conversion
// happen before any `process.beforeScript` or `env { }` scope takes
// effect, and home directories on this cluster are too small to hold
// a fresh apptainer working set. See docs/mcw_rcc.md.
apptainer {
    enabled    = true
    autoMounts = true
    runOptions = '-B /scratch,/hpc'
    // Persistent image store: one shared `.img` cache per user under
    // /scratch. Equivalent to setting NXF_APPTAINER_CACHEDIR; doing it
    // here means users don't have to export it. Apptainer's own
    // pull-time cache (APPTAINER_CACHEDIR / APPTAINER_TMPDIR) still
    // needs to be exported in the driver script — see docs/mcw_rcc.md.
    cacheDir   = "/scratch/g/${System.getenv('USER')}/nf-apptainer-cache"
}
 
// Task-side safety net: any container engine operation that happens
// inside a process picks up these paths. Users who forget to export
// them in the driver script will still see the driver-side pull
// fail, but task re-pulls and any in-task apptainer use are routed
// to scratch automatically.
env {
    APPTAINER_CACHEDIR = "/scratch/g/${System.getenv('USER')}/.apptainer/cache"
    APPTAINER_TMPDIR   = "/scratch/g/${System.getenv('USER')}/.apptainer/tmp"
}
 
process {
    executor = 'slurm'
 
    // Single-node ceilings: bigmem nodes are 48 CPU / 1.5 TB; max walltime 7 days.
    resourceLimits = [
        memory: 1500.GB,
        cpus  : 48,
        time  : 168.h,
    ]
 
    // Apptainer replaces the old `singularity` module on this cluster.
    beforeScript = 'module load apptainer/1.4.1'
 
    cache = 'lenient'
 
    queue = {
        def mem_per_cpu_mb = task.memory ? (task.memory.toMega() / task.cpus) : 0
        if ((task.memory && task.memory > 360.GB) || mem_per_cpu_mb > 7680) {
            return 'bigmem'
        }
        return 'normal'
    }
 
    withLabel: 'process_gpu' {
        queue            = 'gpu'
        clusterOptions   = '--gres=gpu:1'
        containerOptions = '--nv'
    }
}
 
executor {
    queueSize       = 100
    submitRateLimit = '10 sec'
    pollInterval    = '30 sec'
}