Introduction

In nf-core, we’ve been excited to adopt Wave to automate software container builds, and have been looking for the right way to do it. With the announcement of Seqera Containers we felt it was the right time to put in the effort to migrate our containers to be built using Wave, using Seqera Containers to host the container images for our modules. You can read more about our motivation for this change in Part 1 of this blog post.

Here, in Part 2, we will dig into the technical details: how it all works behind the curtain. You don’t need to know or understand any of this as an end-user of nf-core pipelines, or even as a contributor to nf-core modules, but we thought it would be interesting to share the details. It’s mostly to serve as an architectural plan for the nf-core maintainers and infrastructure teams.

Too Long; Didn't Read
  • Module contributors edit environment.yml files to update software dependencies
  • Containers are automatically build for Docker + Singularity, linux/amd64 + linux/arm64
  • Conda lock-files are saved for more reproducible and faster conda environments
  • Details are stored in the module’s meta.yml
  • Pipelines auto-generate Nextflow config files when modules are updated
  • Pipeline usage remains basically unchanged

The end goal

Before we dig into how the details of how the automation will work, let’s summarise the end goal of this migration.

Glossary

  • linux/amd64: Regular intel CPUs (aka x86_64)
  • linux/arm64: ARM CPUs (eg. AWS Graviton, aka AArch64). Not Apple Silicon.
  • Apptainer: Alternative to Singularity, uses same image format
  • Mamba: Alternative to Conda, uses same conda environment files
  • Conda lock files: Explicit lists of packages, used to recreate an environment exactly.

Usage summary

Pipeline users will see almost no change in current behaviour, but have several new configuration profiles available.

nf-core profileStatusUse case
dockerUnchangedDocker images for linux/amd64
podmanUnchangedDocker images for linux/amd64
shifterUnchangedDocker images for linux/amd64
charliecloudUnchangedDocker images for linux/amd64
docker_armNewDocker images for linux/arm64
podman_armNewDocker images for linux/arm64
shifter_armNewDocker images for linux/arm64
charliecloud_armNewDocker images for linux/arm64
singularityUnchangedSingularity images for linux/amd64
apptainerUpdatedSingularity images for linux/amd64 (not Docker, as previously)
singularity_armNewSingularity images for linux/arm64
apptainer_armNewSingularity images for linux/arm64
singularity_orasNewSingularity images for linux/amd64 using the oras:// protocol
apptainer_orasNewSingularity images for linux/amd64 using the oras:// protocol
singularity_oras_armNewSingularity images for linux/arm64 using the oras:// protocol
apptainer_oras_armNewSingularity images for linux/arm64 using the oras:// protocol
condaUpdatedConda lock files for linux/amd64
mambaUpdatedConda lock files for linux/amd64, using Mamba
conda_armNewConda lock files for linux/arm64
mamba_armNewConda lock files for linux/arm64, using Mamba
conda_envNewConda with local environment.yml resolution
mamba_envNewConda with local environment.yml resolution, using Mamba

Conda lock files

Conda lock files were mentioned in Part I of this blog post.

These pin the exact dependency stack used by the build, not just the top-level primary tool being requested. This effectively removes the need for conda to solve the build and also ships md5 hashes for every package. This will greatly improve the reproducibility of the software environments for conda users and the reliability of Conda CI tests.

They look something like this:

FastQC Conda lock file for linux/amd64
# micromamba env export --explicit
# This file may be used to create an environment using:
# $ conda create --name <env> --file <this file>
# platform: linux-64
@EXPLICIT
https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2#d7c89558ba9fa0495403155b64376d81
https://conda.anaconda.org/conda-forge/linux-64/libgomp-13.2.0-h77fa898_7.conda#abf3fec87c2563697defa759dec3d639
https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-2_gnu.tar.bz2#73aaf86a425cc6e73fcf236a5a46396d
https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-13.2.0-h77fa898_7.conda#72ec1b1b04c4d15d4204ece1ecea5978
# .. and so on

Singularity: oras or https?

Unfamiliar with oras://? Don’t worry, it’s relatively new in the field. It’s a new protocol to reference container images, similar to docker:// or shub://. It allows Singularity to interact with any OCI (Open Container Initiative) compliant registry to pull images.

Using oras has some advantages:

  • Singularity handles pulls in the process task, rather than in the Nextflow head job
    • This means less resource usage on the head node, and more parallelisation
  • Singularity can use authentication to pull from private registries (see Singularity docs for more information).

However, there are some downsides:

  • Shared cache Nextflow options such as $NXF_SINGULARITY_CACHEDIR and $NXF_SINGULARITY_LIBRARYDIR are not used
  • Singularity must be installed when downloading images for offline use
  • oras:// is only supported by recent versions of Singularity / Apptainer

As such, we will continue to use https downloads for Singularity SIF images for now. However, we will start to provide new -profile singularity_oras profiles for anyone who would prefer to fetch images using the newer oras protocol.

If you’d like to know more, check out the amazing bytesize talk by Marco Claudio De La Pierre (@marcodelapierre) from June 2024:

Modules

All nf-core pipelines use a single container per process, and the majority of processes are encapsulated within shared modules in the nf-core/modules repository. As such, we must start with containers at the module level.

GitHub issue

For the latest discussion and progress on bulk-updating existing nf-core modules, see GitHub issue nf-core/modules#6698.

Changes to main.nf

With this switch, we simplify the container declaration, listing only the default container image: Docker, for linux/amd64. There will no longer be any string interpolation or logic within the container string.

The container string is never edited by hand and is fully handled by the modules automation.

With the FastQC module as an example:

main.nf
process FASTQC {
     label 'process_medium'
 
     conda "${moduleDir}/environment.yml"
+    container "fastqc:0.12.1--5cfd0f3cb6760c42" // automatically generated
-    container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ?
-        'https://depot.galaxyproject.org/singularity/fastqc:0.12.1--hdfd78af_0' :
-        'biocontainers/fastqc:0.12.1--hdfd78af_0' }"
 
     input:
     tuple val(meta), path(reads)

We considered removing both conda and container declarations from the module main.nf file entirely. However, we see benefit in keeping these in this form because:

  • It’s clearer to those exploring the code about what the module requires.
  • The container string is needed to tie the module to the pipeline config files (see Building config files below)

Removing the container logic from the string should be a big win for readability.

Changes to meta.yml

Through the magic of automation, we will append and then validate the following fields within the module’s meta.yml file. Following the FastQC example from above:

meta.yml
# ..existing meta.yml content above
containers:
    docker:
        linux_amd64:
            name: community.wave.seqera.io/library/fastqc:0.12.1--5cfd0f3cb6760c42
            build_id: 5cfd0f3cb6760c42_1
            scan_id: 6fc310277b74
        linux_arm64:
            name: community.wave.seqera.io/library/fastqc:0.12.1--d3caca66b4f3d3b0
            build_id: d3caca66b4f3d3b0_1
            scan_id: d9a1db848b9b
    singularity:
        linux_amd64:
            name: oras://community.wave.seqera.io/library/fastqc:0.12.1--0827550dd72a3745
            https: https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/b2/b280a35770a70ed67008c1d6b6db118409bc3adbb3a98edcd55991189e5116f6/data
            build_id: 0827550dd72a3745_1
        linux_arm64:
            name: oras://community.wave.seqera.io/library/fastqc:0.12.1--b2ccdee5305e5859
            https: https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/76/76e744b425a6b4c7eb8f12e03fa15daf7054de36557d2f0c4eb53ad952f9b0e3/data
            build_id: b2ccdee5305e5859_1
    conda:
        linux_amd64:
            lock file: https://wave.seqera.io/v1alpha1/builds/5cfd0f3cb6760c42_1/condalock
        linux_arm64:
            lock file: https://wave.seqera.io/v1alpha1/builds/d3caca66b4f3d3b0_1/condalock

All images are all built at the same time, avoiding Conda dependency drift. The build and scan IDs allow us to trace back to the build logs and security scans for these images.

The Conda lock files are a new addition to the nf-core ecosystem and will help reproducibility for Conda users. These are generated during the Docker image build and are specific to architecture. The lock files can be accessed remotely via the Wave API, so we can treat them much in the same way that we treat remote container images.

Pipelines

Container information at module-level is great, but it’s not enough. Nextflow doesn’t know about module meta.yml files (they’re an nf-core invention), so we need to tie these into the pipeline code where they will run.

The heart of the solution is to auto-generate a config file for each software packaging type (Docker, Singularity, Conda) and platform (linux/arch64 and linux/arm64). These will be created by the nf-core/tools CLI and never be edited by hand, so no manual merging will be required. They’ll simply be regenerated and overwritten every time a version of a module is updated.

Each config file will specify the container or conda directive for every process in the pipeline:

config/containers_docker_amd64.config
// AUTOGENERATED CONFIG BELOW THIS POINT - DO NOT EDIT
process { withName: 'NF_PIPELINE:FASTQC'         { container = 'fastqc:0.12.1--5cfd0f3cb6760c42' } }
process { withName: 'NF_PIPELINE:MULTIQC'        { container = 'multiqc:1.25--9968ff4994a2e2d7' } }
process { withName: 'NF_PIPELINE:ANALYSIS_PLOTS' { container = 'express_click_pandas_plotly_typing:58d94b8a8e79e144' } }
//.. and so on, for each process in the pipeline

Likewise, the conda config files will point to the lock files for each process:

config/conda_lock files_amd64.config
// AUTOGENERATED CONFIG BELOW THIS POINT - DO NOT EDIT
process { withName: 'NF_PIPELINE:FASTQC'         { conda = 'https://wave.seqera.io/v1alpha1/builds/5cfd0f3cb6760c42_1/condalock' } }
process { withName: 'NF_PIPELINE:MULTIQC'        { conda = 'https://wave.seqera.io/v1alpha1/builds/9968ff4994a2e2d7_1/condalock' } }
process { withName: 'NF_PIPELINE:ANALYSIS_PLOTS' { conda = 'https://wave.seqera.io/v1alpha1/builds/58d94b8a8e79e144_1/condalock' } }
//.. and so on, for each process in the pipeline

The main nextflow.config file will import these config files, depending on the profile selected by the person running the pipeline.

Singularity will have separate config files and associated -profiles for both oras and https containers, so that users can choose which to use.

Local modules and any edge-case shared modules that cannot use the Seqera Containers automation will need the pipeline developer to hardcode container names and conda lock files manually. These can be added to the above config files as long as they remain above the comment line:

// AUTOGENERATED CONFIG BELOW THIS POINT - DO NOT EDIT

We’re also taking this opportunity to update the apptainer and mamba profiles too, they will import the exact same config files as the singularity and conda profiles.

Here’s roughly how the nextflow.config file with the -profile config includes will look:

nextflow.config
Note

Boilerplate code (eg. disabling other container engines) has been removed from this blog post code snippet for clarity. It will still be included in the pipelines. We may move this whole code block into it’s own separate config file with includeConfig so that the main nextflow.config file is easier to read.

nextflow.config
// Set container for docker amd64 by default
includeConfig 'config/containers_docker_amd64.config'
 
profiles {
    docker {
        docker.enabled      = true // Use the default config/containers_docker_amd64.config
    }
    docker_arm {
        includeConfig       'config/containers_docker_linux_arm64.config'
        docker.enabled      = true
    }
    // podman, shifter, charliecloud the same as docker - also with _arm versions
    singularity {
        includeConfig       'config/containers_singularity_linux_amd64.config'
        singularity.enabled = true
    }
    singularity_arm {
        includeConfig       'config/containers_singularity_linux_arm64.config'
        singularity.enabled = true
    }
    singularity_oras {
        includeConfig       'config/containers_singularity_oras_linux_amd64.config'
        singularity.enabled = true
    }
    singularity_oras_arm {
        includeConfig       'config/containers_singularity_oras_linux_arm64.config'
        singularity.enabled = true
    }
    apptainer {
        includeConfig       'config/containers_singularity_linux_amd64.config'
        apptainer.enabled = true
    }
    apptainer_arm {
        includeConfig       'config/containers_singularity_linux_arm64.config'
        apptainer.enabled = true
    }
    apptainer_oras {
        includeConfig       'config/containers_singularity_oras_linux_amd64.config'
        apptainer.enabled = true
    }
    apptainer_oras_arm {
        includeConfig       'config/containers_singularity_oras_linux_arm64.config'
        apptainer.enabled = true
    }
    conda {
        includeConfig       'config/conda_lock files_amd64.config'
        conda.enabled       = true
    }
    conda_arm {
        includeConfig       'config/conda_lock files_arm64.config'
        conda.enabled       = true
    }
    conda_env {
        conda.enabled       = true // Use the environment.yml file in the module main.nf
    }
    mamba {
        includeConfig       'config/conda_lock files_amd64.config'
        conda.enabled       = true
        conda.useMamba      = true
    }
    mamba_arm {
        includeConfig       'config/conda_lock files_arm64.config'
        conda.enabled       = true
        conda.useMamba      = true
    }
    mamba_env {
        conda.enabled       = true // Use the environment.yml file in the module main.nf
        conda.useMamba      = true
    }
}
 
docker.registry      = 'community.wave.seqera.io/library'
podman.registry      = 'community.wave.seqera.io/library'
apptainer.registry   = 'oras://community.wave.seqera.io/library'
singularity.registry = 'oras://community.wave.seqera.io/library'

Note that there are a few changes here:

  • New profiles with _arm suffixes for linux/arm64 architectures
  • New profiles for _oras suffixes for using the oras:// protocol
  • The apptainer profiles now uses the singularity config files
  • The conda profiles now use Conda lock files instead of environment.yml files
  • New conda_env profiles for those wanting to keep the old behaviour
  • New mamba profiles, using the conda config files
  • Base registries set to Seqera Containers

Because we’re only defining the image name and making use of the base container registry config option, it should still be simple to mirror containers to custom Docker registries and overwrite only docker.registry as before.

Automation - Modules

The nf-core community loves automation. It’s baked into the core of our community from our shared interest in automating workflows. We have linting bots, template updates, slack workflows, pipeline announcements. You name it, we’ve automated it.

In these sections, we’ll cover how we’re going to build all of these shiny new things without manual intervention.

GitHub issue

For the latest updates on modules container automation, see nf-core/modules#6694.

Updating conda packages

The automation begins when a contributor wants to add a piece of software to a container. For instance, they decided that they need samtools installed. The contributor updates the environment.yml and adds a line with samtools:

environment.yml
channels:
    - conda-forge
    - bioconda
dependencies:
    - bioconda::fastqc=0.12.1
    - bioconda::samtools=1.16.1

That will kick off the container image generation factory (it could equally be a change to remove a package, or change a pinned version).

A commit will be pushed automatically with an updated meta.yml file pointing to the new containers, plus new nf-test snapshots for the software version checks.

Only the interesting part needs to be edited by the developer (which tools to use) and all other steps are fully automated.

Container image creation

As with most automation in nf-core, container creation will happen in GitHub Actions. Edits to a module’s environment.yml file will trigger a workflow that uses the wave-cli to build the container images.

  1. GitHub Actions identifies changes in the environment.yml file.
  2. wave-cli is executed on the updated environment file.
  3. Seqera Containers builds new containers for various platforms and architectures.
  4. GitHub Actions runs stub tests commits the updated the version snapshot.
eyJ2ZXJzaW9uIjoiMSIsImVuY29kaW5nIjoiYnN0cmluZyIsImNvbXByZXNzZWQiOnRydWUsImVuY29kZWQiOiJ4nOVcXOtT2kpcdTAwMWL/3r+C8Xw9pHu/dOadd1x1MDAxMFx1MDAxNPCuaNW+c8aJJEBKSCBcdIKe6f/+blwiklx1MDAxMFxiXHUwMDA2XHUwMDA1S1tsvexudp/sPr/nurv/fipcdTAwMTR2gse+ufOlsGOOm7ptXHUwMDE5nj7a+TssfzA933JcdTAwMWRVhaK/fXfoNaOWnSDo+18+f+7pXtdcZvq23jS1XHUwMDA3y1x1MDAxZuq2XHUwMDFmXGZccsvVmm7vs1x1MDAxNZg9/7/h91x1MDAxM71n/qfv9ozA0+JBiqZhXHUwMDA1rvc8lmmbPdNcdHzV+//U34XCv9H3XHUwMDA0dZ7ZXGZ0p22b0Vx1MDAwM1FVTCChXCJdeuI6XHUwMDExsVxmcSDUfzxtYPlcdTAwMTU1XFxgXHUwMDFhqralSDbjmrBoZzA+1Xu4U959PDt9vCm1XHUwMDA3ptXvxqO2LNtuXHUwMDA0j3ZEle+ql4nr/MBzu+a1ZVx1MDAwNJ2XaUuUZz3lucN2xzF9f+ZcdTAwMTm3rzet4FGVQTAtfJ6BL4W4ZFx1MDAxY74j11x1MDAwNFx1MDAxMlx1MDAxMNFpafhcdTAwMThnmsRMqHKEmcREpsgpu7aafkVO4OmO39c9tVx1MDAwMDFR93qz21aUOca03V86NUSrXHUwMDE1t1x1MDAxOU1elFx0qlx1MDAxMYmg4DFcdTAwMDVcdTAwMWTTandcdTAwMDJViVx1MDAxOdQ4YpQlyPPNaPYhl1x1MDAwMlx1MDAxMEFp/DrhkP26XHUwMDExMcI/8Zx7ioXq4SPO0LaTXHUwMDEz51x1MDAxOJOJe2GYmGXwpORH/E5h+71cdTAwMDSrxSNcZvuGXHUwMDFlTIhCnFx1MDAxMCk5RSSm2Lacbnp42212Yy76lFx1MDAxOGs17kWUZHEvhIhDzCXNz77VRtsy7pqXtHpcdTAwMDfPL7zTeu2i6mewb9Nzfb/Y0YNmZ/NMnIOLXHUwMDE5oIQluWjCx1hcIspnXHUwMDE4PMXCf0HJYVx1MDAxMy1l34Vs/l5cdTAwMTbGmFx1MDAxM1x0XHUwMDEwYX80XHUwMDA3XHUwMDEzXHUwMDAyMjmYci5cdTAwMDGHXCI/XHUwMDA3P1xcXn3rXHUwMDFk12/Lg5LfXHUwMDEwbVrSeYN8lFx1MDAwMIZv4l2kYcE4TLNuXHUwMDExc01cdTAwMDK2jG/N8OvtfIuQ0JL9T3mWQlxyLOJXxeJcdTAwMTjAhDr8WH6dPlx1MDAxMz+dYLLAXHUwMDFjx++Y4IjxxWjfLkP7rMq9cWN4su+X7dLOtN2PyW9cdTAwMWZcdTAwMDaGXHUwMDE5Olx1MDAxMzjgkGbhXHUwMDAwXHUwMDAxzlx1MDAwMMZI5obB4pfeblx1MDAxOFxiTVx1MDAwMEElTuNcdTAwMDBcdTAwMTEtWbZuXHUwMDE0QE61Wf0wxVx1MDAwMVwiaVxiKOZX5pDgZFxyXHUwMDE4mKnYqHCOqXKdoGE9PfPUTOm+3rPscLrjXHUwMDE3i1x1MDAxOFXNntMqNl3P/NxzjaFtJsyBsEHJttpOZFx1MDAwZSjCTW+GsVx1MDAwM0vZ69NcdTAwMDY9yzCSXHUwMDFhoKlG1S3H9Op5XHUwMDA0t+tZbcvR7cvXiFKzYdZeXHUwMDE2XHUwMDEwaijBXHUwMDE3vlx1MDAxOVZG7/4mhELOMzVcdTAwMTVgkCpDQ8a8/1x1MDAxYUS9qlP3jnqV7mO5d2mcjfF+1TvbbohizGZcdTAwMTVDxDBCk1xip3yItYNcdTAwMTRcdTAwMDCNQSmEUkCUMVx1MDAwMmKbKUbrvI+AOVwiXG4xOPbwkmiNddDXyknt+9nj3ddGu9g7a13DXXxLXnTEb1x1MDAwMOp4uV9ArdjLLIRetGlkXHUwMDAw2jZbwVx1MDAxMjhcdTAwMDduP1x1MDAwYsszRKeBu3DgNGhpglGmqIXibajFKF36glrl30JcbqmIh3tccrT718bhuFuWnb71dCf2XHUwMDBm4EmtVtxu0EJCNSpcdTAwMTDnQmIwXHUwMDBinY+D72o6lkOCiczw7P9cdTAwMWPUzqti03mwPNdcdCnTXHUwMDFle/bHXCI3c/A1qNyo1ULwZoc3XHUwMDAw4Fx1MDAwMIVcdTAwMDI+N3o79PTaqVT92smpbVx1MDAxY1SN8tdcdTAwMTKrbjl6IdVcdTAwMTBKxVx1MDAwZqJHsTKXIzhv0kPUXHUwMDA0QZBcdTAwMDNJMCDqXHUwMDBimkVcdTAwMTgvyFx1MDAxNMOEXHUwMDEy5UhCMGO7T9BMKeVcZrF4xtboM6JJyVx1MDAwN8U4XHUwMDEy06x7wa7lXHUwMDE4ltNOP2I6RkaNrftB2e31rECRceZaTpBuXHUwMDEx9VvyPHfUMfW5uVA9Z9b1w+5mxWP8WyHmqeiP6e///L2wdXHpsoef5ILHvX1K/sxCumnbVt9fXGJ2mVx1MDAxOYiHUtl+jFx1MDAxM5pcdTAwMWbrZqV+cClcdTAwMDfjRvm8S89huyUuW8MtxzqAmqBcdTAwMGKimFhqXHUwMDAyXG4861x1MDAxYr9cdTAwMDHrf92jXHUwMDE2ur9fgHOqpXpPWNXpqmlcdTAwMDBeKFx1MDAwNClXeCPhy1x1MDAxY9CePjNcdTAwMTNcdTAwMGV6Xv3GLfNM9+CxNTja2288PO2Px+hxNjr0wpB6iKktjFx1MDAwN0HGMuFAIGJcdTAwMWNcdTAwMDKa33Bt+KNyI3hcdTAwMThcXJtj0TsplfH36uHRdsNcdTAwMDExvFx1MDAxOFx1MDAwZZBcdTAwMDJcclx1MDAwMVxuXonqv1x1MDAwN1x1MDAwZlx1MDAxMmucSj6rdpd4mpQpL1x1MDAwMlx1MDAwMLiGZNT7bFG2XHUwMDAyW77Pg7zWXHUwMDFmzEL5qF5omUGzY/pcdTAwMDXHXHUwMDFjXHUwMDE1rJ7ezlxmXHUwMDExbcguzUXIJj1MxHG6dIpUXHUwMDA2JFx1MDAwN1x1MDAxMPP8iqtcXLrpVjp7OChWLzrYXHUwMDFkfv12TC+2XHUwMDFmqXyRkYpcdTAwMDTTqIRcXM5geJ1IxSRCKiM4LSmWxYVcdTAwMTiEQHAm1qC63lxiVyUs1HwlZnbTcHVaxcD0g8IzXHUwMDFkfmHCqH7Bd/S+33GDj1x1MDAwNe1cbuRsXHUwMDEyulwiXHUwMDFiuVKCkKd5/ohucF7TL0vVxvi0T46LI//m7Pa4sf3IXahjXHUwMDExXHUwMDE0XHUwMDFhloJsyuJkyqzEUmKKlVx1MDAwZo+TXHUwMDEx2iW4RUAoPVx1MDAwYqmMfdDFkaFcbqlUvpUqZWnv2sNqcXD1iGp7PyEy9HHa+DjKg3wshtNjbjSKSzOtYY4wooSvXHUwMDEwxa2Ozk5u+1xy/8CooHr93rns8f7hdlx1MDAwM1x1MDAxNVx1MDAxM6BRplx1MDAxY2BcbiXF6e1aXGLKjaI1ModcdTAwMTcgdEHsllwirvCc4Vx1MDAxNP45XGKdj932zED/+KDt/KibTJDSzK08VFx1MDAwMqCsLpl/XHUwMDBiQ/umwbt4XGJcdTAwMTDdPVx1MDAxY53u1Y7YU9PZbpBcdTAwMTKCNIRcdTAwMTVKwWKQblalXCIuNMmV08qEklx1MDAxM1x1MDAwMMfvsMxcdTAwMTRcdTAwMDaMXHUwMDEwXCJ4huf651x1MDAwMHZepU7GL4ysoFx1MDAxM/mUU9h9sIObi5B1KN8lsVmRmURVblx1MDAxNIWYgvzq176rndxcdTAwMTdZa+D1sXtcdTAwMGWM8tne+Xo9XFzDXHJm8strMJRDXHJL4XxcdTAwMWGGSI3QdcSisjZII6xRXGL54t2liyqnu/VcdTAwMDTCQKwjJLVN4dm/l/V77ZNH/1x1MDAxOJ407112NGhccthBf7S31rDvKuJnKdaex1+ANIyy475qSVx1MDAwNSN4hVx1MDAwM1x0y2d6K7VolPKETDLGXHUwMDEwh0phzlwijjFNVUJcdTAwMDZcYpVRo42gXHUwMDBlQqJcdTAwMWNjXHUwMDEwRmookJBcdTAwMGI+XHUwMDBmPlx1MDAwZTVBJCXKKyWQXHUwMDEzIdJcdTAwMTAkUjCglmxbk59v0qWp5GeCsMlBnHqO/FukfZvDkEqg8XBLXHUwMDE3hYxcdTAwMTPGlTxcdTAwMTWJRm29XHUwMDFmqTNcdTAwMDCEmmLEXGKCysHA85uFZ9KuWTQtVzwzNFFEXHUwMDA1xkq1XHUwMDExpdto7HzGNEEtYkCIMUFAyeB5mtac8E2LjLXmfJVcIlx1MDAwMUxAIVx1MDAxOUJcdTAwMDJAXHUwMDFhp5aiWqKFkWyobEuqpI/y717rL8xlSsSVy/rSYbI/jDRcYpTbjiXM218mXHUwMDFlw89cdTAwMWNcdTAwMTLj7j4lf77B8IFcdTAwMTSmS1/EMVx1MDAwNlx1MDAwNEmGV1xiPNyxxnH/XG5+1SuXnT16elxirotcdTAwMWTjXHUwMDE3tXxCd4Zjmq7aXG7ThylccqGcsW3MTL/DRFlq+phXjb1DVPQuykegXHUwMDA2XHUwMDAzXHUwMDFmXHUwMDFmXlx1MDAxZNW30/RZgjaUWMy5Q1x1MDAxMMqtxpCxXHUwMDE1XHUwMDBlXHUwMDAz9c/twW3wcNetiidcdTAwMTNcZmxxcL/eU1x1MDAxMFx1MDAxZlxiNyE0sH1QXHUwMDBic1hMZkXkfy7U3lx1MDAwMYmlUDvffVx1MDAxYUn74fA7LHGzLm9kfbe6u51Qy/QymMzCmeIxXHUwMDAyMKc4f8p6uUzbSidDucaakFhcbo5cdTAwMTFcdTAwMDBcdTAwMTSClFvPlXJTc/DuXHUwMDAzeJmAXHUwMDAz8zDDWJvbiD2J0lGAlVx1MDAxZiTp4oNcZr+3J7GC1Vx1MDAwZaBcYk1XtbBcYilcdTAwMTNRLrDbqSaAXHUwMDE0XHUwMDEyXHUwMDEwrtaXQs7nXj6XK7HckktcdTAwMTBVzEVcdTAwMTXTKFx1MDAxN4rbXHUwMDA04Fx1MDAxYzIk4Vx1MDAxY1W/kjMx0zrm6lx1MDAxNY3yTNlcdTAwMDVlpknOXHUwMDExXHUwMDEwUFx1MDAxMpw/zbBcXElsqeyCXHUwMDFhkphhzrHyi1x1MDAxMZ890YGIUDNOUpvX1mos5NtcdTAwMTauRCxBi4xcdTAwMDYpXHUwMDE0XHUwMDAwOcA/y2j4mdJsNcFBXHUwMDA1XHUwMDAxiFxuwYVcdTAwMDAgjiMl5Fx1MDAwNsSCMaLWXFw5qlx1MDAxOFxuOvf2ucTZcks5JWNjosLLJSCfo0pqSMlYXGIxXHUwMDA3RDGh+LWl2evb4aeMvqKEW+JcYmGcfStcdTAwMDIkXHUwMDE0cPUvvyNkVnSGd8tX39tcZjzdt2zv4NCpbbeQg1x1MDAwMIXH1sBcXFx1MDAxYVx1MDAxNVx1MDAwYq4xwshcdTAwMDZcdTAwMTOpb9hcci8540qFb6FcdTAwMTf0O3grgmRcdTAwMWZcdTAwMDOTStVBRml+lb98RrZcdTAwMTJccqG7Mn81XHUwMDAyhnRNUMhMg1x1MDAwMKFcdFx1MDAxZYpxKFx1MDAxOVx1MDAwN0pcdTAwMDfNY4IpV0lpJ6jsXHUwMDExhVfA5tIgnFx1MDAwMVx1MDAwNpXg/Fx1MDAxM9MguVx1MDAxNatS9yy8/4JAophZhLGuOb1KNFx1MDAxOFItJCNcdTAwMTBw9sYsyHJlMEtcdTAwMTLGQtmZVKk+XHUwMDFh3qM0b4BcdTAwMTCNXGKAMSVcYmOC6YLMzC+l6iHWwu1rXHQ4hVx1MDAxZlx1MDAwNLVwXHUwMDAyZlxcmIxcdTAwMGVcdTAwMDTR5p/HTENpXHUwMDAzIYuATMCFnzmorWhwZG3cXCJLtneot4ZEwvzGRne4Z9/cdVx1MDAwNn2v9n1cXLdHt7XL5LaYbVx1MDAxNK+QoHDZXHUwMDE4wOL5oF2sbl5MXHUwMDBljNewd2vpYVssuSakZMqwRiD//i0sqJCvb7i89q9cdTAwMWVO6rp9Yd4+XHUwMDFj+4f8Ru/y5m+9f+tqsm0q3Fx1MDAwMlnQXHUwMDFkY3q0wC80X+TRx27kWo2ijW6nXrLJRFDGyUq3Xn4r0cvvTSq9796ga6Ldq1x1MDAxYnjqZiB+q65cclR6Vlx1MDAwMyBcdTAwMTXlXHJcdTAwMWZcdTAwMTVM+bl8w/dcdTAwMDZColx1MDAxNDpcZkPQIP/RJS45Vssj1+Bq/CpArlpBbXhfKDWD8ITQx0I2a+x1gDPr0lx1MDAwYp59XHUwMDBlPtryxJWtlVx1MDAxYpu0XHTOJbGu4LdcdTAwMDNedK5cdTAwMDSuyPusa6beg81lmdC3gTPa8jGxu2bASThQVifDm72zZlGChmVd5UmkUFZS1kVTv4eL87tdczGbpZgubZZJ/WnS0Y7e7zdcdTAwMDI1x9O1UmttXHUwMDE5XHUwMDEzqVx1MDAxNpO782CZo91cdTAwMDW+div6hKZXtKDRjVjRwv/49OP/LeJlhCJ9 nf-core/modulesfile editedenvironment.ymlWave CLI fetches new imagesnf-test updates versions snapshotModulemeta.ymlupdated with new containersUpdated meta and snapshots committedGitHub Actions

Once this GitHub Actions run completes it will push the new commits back to the PR and the regular nf-test CI will run.

nf-test versions snapshot

One of the primary reasons that we were so excited to adopt nf-test was the snapshot functionality. Every test has a snapshot file with the expected outputs, and the outputs are deterministic (not a binary file, and there’s no dates).

In that snapshot, we also capture the versions of the dependencies for the module (example shown for bowtie2):

main.nf.test.snap
    "versions": {
        "content": [
            {
                "BOWTIE_ALIGN": {
                    "bowtie": "1.3.0",
                    "samtools": "1.16.1"
                }
            }
        ],
        "meta": {
            "nf-test": "0.9.0",
            "nextflow": "24.04.4"
        },
        "timestamp": "2024-09-27T10:42:58.892298"
    },

This gives a second level of confirmation that the containers were correctly generated.

When updating containers, the nf-test snapshot is parsed and compared to the snapshot from before the new containers were built. The snapshot changes are discarded if anything other than the versions key changed, so it should only vary in the software versions reported. If any other changes are detected, the snapshot change will be rejected and not committed back to the PR. Then PR reviewers will see failing tests and need to manually update the snapshot file.

This means that we can automatically commit the updated snapshot file in the PR if the tool output is unchanged, saving the developer from taking this extra step.

Tip

Note that in the above example, the versions are in the snapshot in plain text, not using an md5 hash. This is a change that we will roll out for all modules, as it makes verification in the PR much easier.

Automatic version bumps with Renovate

We’ve recently adopted Renovate, a tool for automated dependency updates. It’s multi-platform and multi-language and has become pretty popular in the devops space. It’s similar to GitHub’s dependabot, but supports more languages and frameworks, and more importantly for nf-core, enables us to write our own custom dependencies.

Renovate runs on a schedule and automatically updates software versions for us based on the specifications we’ve laid out in a common config.

The magic starts with some nf-core automation to add renovate comments to the environment.yml file:

channels:
    - conda-forge
    - bioconda
dependencies:
    # renovate: datasource=conda depName=bioconda/bwa
    - bioconda::bwa=0.7.18
    # renovate: datasource=conda depName=bioconda/samtools
    - bioconda::samtools=1.20
    # renovate: datasource=conda depName=bioconda/htslib
    - bioconda::htslib=1.20.0

These comments will be added through the batch module updates happening this year. Future modules will have these comments added automatically and linted by the nf-core/tools CLI.

The comments allow some scary regexes to find the conda dependencies and their versions in nf-core/modules, and check if there’s a new version available. If there is a new version available, the Renovate bot will create a PR bumping the version, which in turn will kick off the container creation GitHub Action.

The process will be very similar to the diagram laid out above, however we can go a step further: if the new software versions have no effect on the results of the tests, the PR will be automatically merged:

eyJ2ZXJzaW9uIjoiMSIsImVuY29kaW5nIjoiYnN0cmluZyIsImNvbXByZXNzZWQiOnRydWUsImVuY29kZWQiOiJ4nO1d21LjSJO+76dwMJfb1tQps6omYmODM6abQ1x1MDAwM800vfFcdTAwMDchbNlcdTAwMTb4hC1D01x1MDAxM3P5v8U+3T7JZomDZMsyXHUwMDA2ZDC9445owLJKqVJ+mV9cdTAwMWWq/NeHUmkpuulcdTAwMDVLf5SWglx1MDAxZlW/XHUwMDE11vr+9dJH9/5V0Fx1MDAxZoTdXHUwMDBlXHUwMDFkXHUwMDEy8d+D7rBfjT/ZjKLe4I/ff2/7/Ysg6rX8auBdhYOh31x1MDAxYUTDWtj1qt3272FcdTAwMTS0XHUwMDA3/+X+3/XbwX/2uu1a1PeSi5SDWlx1MDAxOHX7t9dcblpBO+hEXHUwMDAzXHUwMDFh/b/p71Lpr/j/lHT9oFx1MDAxYfmdRiuIT4hcdTAwMGYlXHUwMDAyXCLi+Lu73U4srGBcXElcdTAwMDCD6uFcdTAwMTPhYI2uXHUwMDE3XHUwMDA1NTpcXCeZg+RILZ6IY3O6c/41qrROt1x1MDAwZbZ3XHUwMDFhy3i8cphcXLZcdTAwMWW2WofRTStcdTAwMTZr0KW7SY5ccqJ+91wi+DOsRc37eUu9n3dWvztsNDvBYDByTrfnV8Poht7j7OHN2yn4o5S888N9QHFPXGI0Wlx1MDAwYiFcdTAwMDDBPFx1MDAxY41PR+tZJVx1MDAxNKJcdTAwMDKG0uoxsVa7LXpcdTAwMGUkVtT3O4Oe36cnkVxid+ZXL1x1MDAxYSRhp/bwud98qJl6PfnM9d1ccqNcdTAwMDFPWcGNhodjzSBsNCM6XGJGekogcFx1MDAwZahJipRcdTAwMThB/CxcZpeSc2ZcdTAwMTPp3XV7lVqsXHUwMDE2/0pcdTAwMWVAn1x1MDAxNKrizuhcZlut9Cx2anezeK8+iVx1MDAwMsm7d/5Obsx9fj2leMlcdTAwMTWGvZp/q1x1MDAxZlxcXHUwMDBiTbPGXHUwMDE0apVcYtxcbjtcdTAwMTfjl291q1x1MDAxN4lKfUhdK6PLdPpENVx1MDAwNi3z1JhcdTAwMGKmhdWMsZnVeKivXHUwMDAy9mNcdTAwMTlPvlx1MDAwZkz5+Kh7XGb2pvVaaswnqvGjemy0ZzhIw6TliFx1MDAwNkf0WCD3rFx1MDAxMVxcKkvHXHUwMDE5qDw9/o1cdTAwMDfu31RcdTAwMWSeqOv3esyy2qtQeFx1MDAxMjVcdTAwMTlcdTAwMTJhXHRuKSW9014lhVBaSjVcdTAwMGbtXHUwMDE1r6u9qYn1+9FK2KmFncb4KUGnlnOk5Vx1MDAwZqLVbrtcdTAwMWRGJMZ+N+xE45+Ix13u97vXzcDPzFx1MDAwNY2ce6znhkv8hHslv5WSJ1x1MDAxN//x8Pu/Pj7+6cxcdTAwMTNOzv6Q/pmH6qlcdTAwMWVcbng+tMk+a8lJe2aGdms9qFY2T5qHLOzenLZ/nnxbOVx1MDAxYuZAu9rvXHUwMDBlXHUwMDA25aZcdTAwMWZVm/P3U49cdTAwMDJcXFrPSFx1MDAwYlx1MDAwMFYri0Ilw9x6KvAskFx1MDAxMzOKXHUwMDAzIVx1MDAxY2BMslx1MDAxNMSt5lXxfIhPd1VWeIZgw1xyRyPJK42DXYI1lsRMxH9dV/Vwzl8pXHUwMDA1vleOTquxXHUwMDFiwLezvXW/cXi9v2E/t9tLaU1/UFnfoWzp4cjf93r+Wo5wOmQgl9RJZiSgUbM7w63m8qY5XHUwMDFk1tfs9dnpRkU32vVBc7GdYVkpj2mNWjMlLLJcdTAwMTRhclx1MDAwM1x1MDAxOE9qrrlcdTAwMDCBklx1MDAxMauamzMkNfdQTEJcdPdY+sBcdTAwMWQ2XHUwMDA0J+vJJeNvxeMmg+Ne56LgRzSKhltccrla2zbVzVYlOJVcdTAwMTfY3u5cdTAwMWT22uuf31x1MDAxMFx1MDAxYiNypmBhZC4slJFcdTAwMDasXHUwMDA0MTMsJt/0YsOCa1x1MDAwZimgY+JcdTAwMWVcdTAwMTajPkRcdTAwMTBJZHRg/sDgXHUwMDFhPG34JGikfPlcdTAwMWQquCRvwbVOcdpno2LkwOtcdTAwMTDBerdcdTAwMTNcdTAwMWSGP28j6pF3N/x22HJcdTAwMTOfXHUwMDEwnFh1afY69XK121x1MDAwZn5vd2vDVjBYXHUwMDFh+cByK2x0YoJCglx1MDAwN/1cdTAwMTFVj8Kq33r4QDus1dIuokpX9SmE6ldmMe3dfthcYjt+6+gxoVxcVLZ1/1x1MDAwMCmeTunFIIhDNnfvz8KsMDbXlVFITHGDmp38ye1+Q69UN1x1MDAwNlx1MDAwN80rvdZeXHUwMDE5fG+p74uNWWmI9lx1MDAwMdEtJYFYlUxcdTAwMTBcdTAwMTOfb5RcdTAwMDdMknIysmAyraHjkFxymOSSv1x1MDAwMLKMeUjU3lx1MDAxOMlcdTAwMDBRsVx1MDAwNI1cdHYzXHUwMDFlzWWQlOJ5XHUwMDFlLfFRN+WbPooq9KBycVT7dmCvr7707n3IL1x1MDAwMPHk6d9DnLQtKLlMXlDLgXcrqEdTwFx1MDAxZHV7echcdTAwMWVcdTAwMTF6XHUwMDFjxlx1MDAxMy88XHUwMDBlYUgpylx1MDAwM4a5eVx1MDAxZYZTXHUwMDEwXHUwMDFkj+BcdTAwMTjNuVJSJ9r0XHUwMDE4iG29c/M1bC1r3/fbPTZcXGtcZm5OXHUwMDE3XHUwMDFixNxaopwgXHUwMDE4eTJOblx1MDAxN0b56Gui+GmOl3AuOfGF/+fgzfrnoHNcdTAwMTX2u1x1MDAxZCeZd9NuvS6Acy9egFx1MDAxZs5LsCqmx99N6lx1MDAwNFJIppWcnTxvS3u0e777/dtB8+hw97px/cVcXFx1MDAxZS82hjX3lHPE5JBBXHUwMDAwT1x1MDAxNUXc+WA9K6yygJL4NWN2Svql+Fxmq7boSSlcdTAwMTRcdTAwMTfSMqtZJumiXHUwMDAwlKZoeFx1MDAxZWHlP1x01vtj80uwZlx1MDAxZXBy9of0zzxUXHUwMDA3rVbYXHUwMDFiTFx1MDAwNDaX+c6ZWJ5cdTAwMTJcXM7um9eO12qX15VvwdnqZrshO3u95frqYuPaeUTGuFx1MDAwNZn2inE4LMFzc23BkJKS4zVjQj1cdNO/nYm6ODubkCRcdTAwMDJPgpWTkqnZQ/f1PqO5MraIiPhZgH44Z0JcdTAwMTL1uu5f6MrlYX1lb7/dW65cdTAwMWNcdTAwMWatX6zMlkT9OG3cT2rvZPtiU4bfmqv93lEjOMf6zmImZ3OJcCp8XHUwMDFix5pVjLn83+zR7JptfT0t7335ajZWQ769f3JtTlx1MDAwZlx1MDAxNlx1MDAxYmxSoqdcdTAwMDVcYs1cZjLE0fSTVOhxXCJcdTAwMTKoiKByTLPkXCLxRlx1MDAwNNpzRXRi23moy1x1MDAxMGFcdTAwMDFcdTAwMTZcdTAwMTiKXCLq689muGC0lWxcXEWXfnsgjuWw7TeCcrVcdTAwMWb4kdO3XHUwMDAyiG82at1ccq5L8XVcdTAwMDal/yjd6XBp0PF7g2Y3ystTzYlcdTAwMDfPKktcdTAwMTGx7TRcdTAwMGaamr1cZjVcdTAwMDbkylwizO5Cq8fBdm1/uLH1c//U9Ex5/8CEYaGornWjkdD/5bBcdTAwMTZWeFx1MDAxNL66tDJcdTAwMTOg2Fj3gVTgUVx1MDAxYymsXHUwMDE2llu0L/OjeVx1MDAxZDRCesC5nlxclpx08L4gqVx1MDAwNDl4+1bc+C1cXOle8HMlOvjRvTzc2Vx1MDAxMnuyMzCfccZxXHUwMDFmd6WuTSpcdTAwMTWBPNtOTYXjrViT4lSZXHUwMDBiRiDmjOR7Zlx1MDAwZlOnz38hWPRcdTAwMDfNYrHIjfEkXHUwMDEy5LjjXHUwMDEz1qgxUutaNVx1MDAwNLNcdTAwMDRDcoR2TlgkXHUwMDFl41x1MDAxMWNcdTAwMDarjKZcdTAwMTjCplxceVx1MDAxMrRyXHUwMDBmXHSQXHUwMDE0MVx1MDAwYk02QeA4MFFcdTAwMDBcIlx1MDAxNFL2KTpqRZo7Kcd1/Fx1MDAxOVFrSrC7Rs7KXGaBVOymq0MnJaN5dtNEfIkoLFfapD7U8Huxx2PMKJJcdTAwMWMpmFx1MDAwM6Egc+sj4XKeSNNcdTAwMWTTiEhAXHUwMDE2laieXHUwMDAxV7BcdTAwMDBts1wicU9cdTAwMDGpIM1cIrlcZi6EychUcKA+bjFcbo3VybswNNxYpDthPNV8XHUwMDEzXHUwMDFmVVx1MDAxZSeuyTU9JsnojtVj43HGPSs0oLpcdTAwMWYwPZ5cdTAwMTRcdTAwMWVnQlxuafms4+XC0b0yQEyG+5D++VxmaiR5bvWOkylcdTAwMDYr4Fx02YWvK1x1MDAxZOxW7NHnaLtVaX3/s7m1x1x1MDAxNzzgifPtwlx1MDAwMFx1MDAwN1x1MDAxY8suKFx1MDAwMFdP40TqtSDGZMdkervkXHUwMDAyN8hcclx1MDAxOTh4q37MOVGXqZRo4+C6XHUwMDFj+ltcdTAwMTd7n48/VVx1MDAxNex83/ArQVGUqNjsQi73IfuShzZEZyfYXHUwMDEzXHUwMDFhJadP9GJyXHUwMDFmsoZcdTAwMWVcIpfoXHUwMDEypVrI0Vx1MDAxY7000pNkcZh1/MeYlyEul/ow41kttVx1MDAxNXQlgn2q0fFcdTAwMDF6aD1uXHUwMDA0SUvsjFx1MDAxMVx1MDAxYstU3lx1MDAwNI1iXHUwMDE1XHUwMDA1LHNpXHUwMDA0W3DuMzPRKHOPK0R6lsRvnFx1MDAxMWUy9ak7pqGIaVxi52pcZjlTRpTkeexnuu9JXHUwMDBixTzp1I/wJsG5fJulP8pcdTAwMTPgTCxKw8Ayi1x1MDAxOZneXHUwMDEz+ylz6Vx1MDAxOVxu+UiZgeJcdTAwMGXU0qTPXHUwMDE3XHUwMDE0cChN02BcdTAwMTFjt1wiXHUwMDFmXHUwMDFk0CiPPkm0kN1cdTAwMGWo0uNJ9FAp5Zqgb8d7XFy+XFxIuldcdTAwMDaMT6Q/eclepXO5j6O8VjhGOLM5vvzaadjl48FaJTja+Xa2TMZza8ErK1x1MDAxNGx4xtDkXHUwMDFiXG7kjGOfXHUwMDE5XHUwMDAyJFx1MDAxOWIhXHUwMDA0aGrFVFrtXHUwMDE5Uj9j6EKukDOJXGaNm2BcdTAwMDOOf4tHXHUwMDFil1xmO72qrfw86tvg28r2zUVjv7pZ/4V6XHUwMDFmsingr7fXL7WDyC/5nVqSci1V703W6yaCnybRPFuduM1NP3Ey9eTRhUk06jHQn98w2Dpdu9lbLv/Y341OKrDZ2n9cdTAwMGaLVShw9Shw1YJCfHJvXCJRojhcdTAwMDfFmFx1MDAwN+RcdTAwMWSFkoRKssjjglx1MDAxNbdWhStcbr65Ndp1V03OXGZnkI9EvehJ2Vx1MDAwMsKf91x1MDAwMunNMNpcdTAwMWGelZarLiH7ylWcvGtcdTAwMTdcdTAwMDHTvG4mmV+y4Vxiylx1MDAxMJ3E2VfIbGz1evtfds53XHUwMDBmrv/8MkR5UrEsL1R6XHRMp1x1MDAxNW6eh1PNPKElUTJlJNGRsVxcsUHj0UzQMYJcdTAwMGayOa5cdTAwMDeY0NNcdTAwMTRXilx1MDAxMSaskuFcdTAwMWOZWzH6ZiWbf9qZ3OtcdTAwMDXtTMmzLYpri9wmJs2E1FxcstkzXHUwMDFmm+XG55vhZnS4XHUwMDE3Nc/L+/5uuLu+s+hUm3vEo1x0qdxQQKnMKJAtXHUwMDA1waSdxlx1MDAxMFx1MDAwN5ZKXHUwMDE4OSZYcf3FXHUwMDE08rrMvuZE62f1t2R4mHaNlW/lb1x1MDAwMchcdTAwMDaiTsVr8/a3h916dE1TWLpcdTAwMTWkXHUwMDE0dkqr3U7NL9VIL6uvzp+fIE5cdTAwMTFeeeraVa5yw2aNqMg9P6Fi0D33xXrrYlx1MDAwNVaaa+u7+1t+77xxtuBQJuqsrVCkkyhcdK12tJeCXHUwMDEzddZcXGiXxFx1MDAwNFwi0emyadFrXHUwMDA1rPVcZnddXHUwMDEzXGa14cAmL2RcdTAwMDWImVx1MDAwM5FcdTAwMDSCfGqe7lx1MDAxN1x1MDAwMFHkb7hcdTAwMTVzqeLObUmr/6lio0tm24fdtcbVj8uVXHUwMDEwLs2stYR3tIw8N47NJ8jWckNcXPBcdPx48mQuNFxuJSPbRveptdtcdTAwMWGIlH80dyWkZ4BcdTAwMTkwglx1MDAwYqVcdTAwMTSX+W1cdTAwMTQvRaFlnlx1MDAwNWuJwFx1MDAxMFx1MDAxZUkkk1x1MDAwNeFcdTAwMDSXyoSQwGVcdTAwMDFcZvm9hLBcdTAwMDdBp3tFXHUwMDEy5HjOly6VfcSTjHvUrDRFOM7cyp9OVZnG41lyJJaP+JFH49mptdDFLP1xwlx0N+SBUFx1MDAxOGWQjWaclLGeVIREbjDupM2F60u7nqyUViEyxjDNZEa7nohyum5cdTAwMWRy8qlC2kNrP2qu7S9cdTAwMWTcvrjIxj3mVo1at/zbXG6pdKqic1djM56SaKwwXFzTY+f2mV1Px3L56PLgantntXd6evzd/7Q5XHUwMDE05cldT8iBwEY2XHUwMDFhXHKSwqqMSMTdXHUwMDE0d23+XHUwMDE0o1x0JPusMzK9p7rfe+h6mlxmR/fKXHUwMDAwMVx1MDAxOe5D+ueTU1x1MDAxMZjPnNy90fVmT0X8VEattzc7rHo5ONo52Tn93vz8avspPneJh3bJfVfdXHUwMDA2QKIto2udXHUwMDAxXVBBllx1MDAxOISwWqVcdTAwMTbEXHUwMDE02vZkdbxZXHUwMDAx0dj8lvBs4l9oXHRKXHUwMDE00XTxbNZETM8k9e65s6Zcclx1MDAxYbi0WilFwSBcdTAwMWGU+sPOx5JP7/T8waD0v//z79dNQ8wszJxcdTAwMTd0KMxnU8w6v8OegOHpXHUwMDFlZFFcdTAwMTd0kG2XXHUwMDE0b4Bik0DsXHUwMDE2Q1x1MDAwYjKcUlxu5nqV5kKnnrugw7qttbQpon5XePviXHUwMDBi2lxmP05cdTAwMWJX3+zVK8vfXHUwMDBl7f7g5GS/9Vx0z+pcdTAwMTJcdTAwMTYz5ZBcdTAwMWLEgJpcdTAwMTbEkMvQ8ITVkdOXiy6k6yRUXHTttNe1qbg7XHUwMDFlXHUwMDAxXHUwMDFkzYFjQMxohmBI+adUzV9cdTAwMTjEuC1+3C6NxGQtz1x1MDAwYmKU4eTmhVs5nd05hFx05MJcdTAwMTSR0l/YICa/QlfAolx1MDAwZYnaMVx1MDAwMTJvXHUwMDEy45VcdTAwMTSZYEKT21x1MDAwM6mZcHtDUbiYnZV/YoliY4nJqHCvXGZcdTAwMWWS4T6kfz59NVx1MDAxYsvdsTBOyrtt/GZvJ5qel17MvI5cdTAwMDDrmFxipzCe4mtMpVTc+Sg9mm5SXHUwMDBieoSkXHUwMDFh+S2EL66FxF2sXFxoXG5dUKNgXHUwMDEzelxiiTNcdTAwMTnLLGh0QSc9nkx8XHUwMDAx6Fx1MDAxYZFS7PLXs4p5tm96/nTE9lx1MDAxMaU0nChcdTAwMWPTXHUwMDE0XHQpPmlBm1x1MDAxMY55XHUwMDFhl9GzXHUwMDE2ePbeZ7LH0/eCLiWZJKSLaCMl45bssZ0gdZl7jOyCiZOM9E/bd22Oy1p4ytJdMEkhKWJSvXMvXHSe5K5AaSxYipf1o8Plw8e9MsApynySXHUwMDE3yO/zslxu6Fx1MDAwNthcdTAwMTN2rTJHw/3W8HBcdTAwMTVPj9eF35C28aXfWWxK6b5cdTAwMTYg3k9cdTAwMDOMcVx1MDAxYlx1MDAwN8FoY4jmmqiGVWjBeVx1MDAxMpxXXpx5SmuhXGZKo016M8iEUqJcdTAwMDfSuU+mlDPpWUbpeluUXHUwMDEyv7LxfDqlvPhsXHUwMDFhtfNmvXK9pdd3Lzp+tFb185alcGVdoV4wJlx1MDAxOVx1MDAxMfRcdTAwMTSmXHUwMDFm1qVQdGFcZlx1MDAwMlx1MDAxMHD4JLv6roxcdTAwMTgnO0X2WjEyUsy4W1x1MDAxYSGV1tl1l0NA1ERcdTAwMDNcdTAwMWbudsq6XHUwMDE0t1x1MDAwYlx1MDAxN1x1MDAxOT6nh27A9HCKqIgr1Ir74Vx1MDAxZVx1MDAxNy9cdTAwMTdcdTAwMTbuNVx1MDAwZYgn2sS89DRg7j5+mlnSXGYrZieU1Vr1bKtTPfNcdTAwMGbha1x1MDAxMG1d43m49ediXHUwMDFiRJpST0sgXHUwMDE37cpDetRcdTAwMWOiXCJr6UpcdTAwMDbEPVx1MDAwMOa1JNcqx2g1SeJKfTPmpt1OMlx1MDAxMlx1MDAwNL7pXHUwMDE2RK9cXNLfXHT6jaBcdTAwMTT1w0Yj6Fx1MDAwN7WSP4y6bT/ONdNcdTAwMTkjXHUwMDFmn3dqekZR5pyYhvzENJFmXG5KpX1CW86+uWJHp35cdTAwMTVcdTAwMWKtvebG/jo7+XO52C92mEdiWnmCXHUwMDFjXHUwMDE5I+BMgC/FilxmlOPo6GznXFzYzLP3XHUwMDE5XCKvwG1qMeNcdTAwMDKlpV+QPv44bdyvZzsryzft6sY6Vyc9XFxcdTAwMGV311xmvrO0NPJcXI/J3VfxMDCzZ6WnT/RipmAkU1x1MDAxZbjNbVx1MDAxNbhcckJgtLVcdTAwMDZZvPeX0taQfqfDqVwiQedWcblGPOY2cEJ6/EF5wpZC0i3wd98/Jd1Xvlx1MDAxMdOdsKU1RXzS+d5fOIwooJeFXHT3dYNIRFx1MDAxN4CsKWRjhThwpMl2W4Qrilx1MDAxOLK9LDNcdTAwMDUw053QaFx1MDAwMMOFilx0KjE0wV0qJiNcdTAwMTQnXHUwMDA1UJZcdTAwMDMoclx1MDAwMELw972tUHma1rtXVt+fXHUwMDE4JUwrgkNu6oSIa7xue/bMyfQwdSFcdTAwMDNcdTAwMDXQXHUwMDE0zym0mkJcdTAwMDG63dFanNuPXHUwMDE5XHJFyNq1cVn2MqNX6OY9XHUwMDE0nLpcXMlC7mc4PYE2oplcdTAwMGJLXHUwMDA3bKo9KLutXHUwMDAzXHUwMDE5JVx1MDAwMuxcdTAwMTO2tJpKkFx1MDAxNpVcdTAwMGag62PkQrv2XHUwMDEwXHUwMDBlY6tG0VxiXG58hEWl3HdcdTAwMDCKl3V45edcdTAwMTSFXHUwMDE3995cdTAwMDNRXHUwMDBmiuZTrj6hXHUwMDAzxkNyTcLt2Y/KQLbhi1wiXHUwMDA1XG5cdTAwMTjgrbDylmRgdsfr9lx1MDAwZlRakF8lTuBcdTAwMTbSZ/2u9TS5Zk5RXHUwMDE5UyS5eGSTnXkuYZ37VjdWuFx1MDAxZIdcdTAwMDRzzcWoR/yxcDrJXHUwMDExjPuORcuNwkdcdTAwMDd0W924jZ1B31x1MDAwZTiSXHUwMDAzlFx1MDAwZWjGpVx1MDAwMWdcdTAwMWUvXHUwMDFmXHUwMDE38YDjiMjjXHUwMDBiXHUwMDFm7q6w5Pd6h5FbyHCvSVx1MDAwNJGwdpfKSZ7D0lVcdTAwMThcXK9MwG89fjlDXHUwMDFl60L85UMxXv7+8Pf/XHUwMDAxgbZEQiJ9 nf-core/modulesfile editedenvironment.ymlNew images + version snapshotsUpdated meta and snapshots committedGitHub ActionsSoftware update in Conda detectedRenovateFull CI tests run, all pass ✅Merge triggered automatically

So: if all tests pass, the pull request is automatically merged without human intervention. In case of test failures, the Renovate bot automatically requests a review from the appropriate module maintainer using the CODEOWNERS file. The maintainer then steps in to fix failing tests and request a final review before merging.

This efficient process ensures that software dependencies stay current with minimal manual oversight, reducing noise and streamlining development workflows. This will hopefully be the end of the “can I get a review on this version bump” requests in #review-requests!

Automation - Pipelines

We now have nice, up to date software packaging for the shared nf-core/modules with minimal manual intervention. However, we need to propagate these changes to the pipelines that use these modules. There are two main areas that we need to address:

Building config files

As described above, pipelines will have a set of config files automatically generated that specify the container or conda environment for each process in the pipeline.

Creation of these files will be triggered whenever installing, updating or removing a module, via the nf-core CLI. The config files will be completely regenerated each time, so there will never be any manual merging required.

The trickiest part of this process is linking the module containers to the pipeline processes. Modules can be imported into pipelines with any alias or scope. We need to match this against the values that we find in the module meta.yml files:

  • Run nextflow inspect to generate default Docker linux/arch64 config
  • Copy this file and replace the container names with the relevant container for each platform, using the meta.yml files that match the Docker container name

This is why we duplicate the default docker container in both main.nf and meta.yml files - it allows us to link the module to the pipeline.

Warning

Currently, nextflow inspect works by executing a dry-run of the pipeline. We can run using -profile test, but any processes that are skipped due to pipeline logic will not be included in the config file.

This is a known limitation and we are currently working hard with the Nextflow team at Seqera on a new version of nextflow inspect which will return all processes, regardless of whether they are skipped or not. This will be a requirement for progression with the Seqera Containers migration.

Note

This process of copying files and using string substituion is a bit of a hack. If you have any ideas on how to improve this, please let us know!

Edge cases and local modules

There will always be edge cases that won’t fit the automation described above. Not all software tools can be packaged on Bioconda (though we encourage it where possible!). For example, some tools have licensing restrictions that prevent them from being distributed in this way. Other edge-cases include optional usage of container variants for GPU support, or other hardware.

Local modules won’t be able to benefit from the Wave CLI automation to fetch containers from Seqera Containers and will have to be manually updated by the pipeline developer.

For these reasons, we will still support custom container declarations in modules without use of Seqera Containers. It will be up to the module contributors to ensure that these are correctly specified and kept up to date manually.

These can be specified in the main.nf file and added to the autogenerated platform-specific config files, as long as they remain above the comment line:

// AUTOGENERATED CONFIG BELOW THIS POINT - DO NOT EDIT

If at all possible then software should be packaged with Bioconda and Seqera Containers. Failing that, custom containers should be stored under the nf-core account on quay.io. The only time other docker registries / accounts should be used are if there are licensing issues restricting software redistribution.

Custom containers can be also be built using Wave in continuous integration, it’s just that they can’t be pushed to the Seqera Containers registry. However, they can be pushed to quay.io automatically. We can do this using a similar mechanism to the automation used for changing environment.yml files, simply replacing it with Dockerfiles (see nf-core/modules#4940).

Downloads

The nf-core CLI has a download command that downloads pipeline code and software for offline use. This has a lot of hardcoded logic around the previous syntax of container strings and will need a significant rewrite.

By the time we get to running this tool, the pipeline has all containers defined in configuration files. As such, we should be able to run the new and improved nextflow inspect command to return the container names for every process for a given platform. Once collected, we can download the images as before.

The advantage of using this approach is that the download logic can be far simpler. Complex syntax for container strings is not a problem, as we can rely on Nextflow to resolve these to simple strings.

Roadmap

This blog post lays out a future vision for this project. It serves as both a rubber-duck for the authors, a place to request feedback from the community, and as a roadmap for developers.

There are many pieces of work that must come together for its completion, including but not limited to:

If all goes well, we hope to have the majority of this work completed by the end of 2024.


Published on
2 October 2024