Repository: zavolanlab/mirflowz
Pull request #154: build: major dependencies update
Author: login: deliaBlue
Created: 2025-05-08T14:26:58Z | Merged: 2025-05-20T09:52:40Z

## Description

Fixes #151 
Fixes #152 
Fixes #153 

## Summary by Sourcery

Migrate the container runtime from Singularity to Apptainer, bump and standardize dependencies and tool versions, simplify environment setups, and update CI workflows, tests, and documentation accordingly.

New Features:
 - Add Apptainer support and remove Singularity usage across workflows and tests.

Bug Fixes:
- Fix mirna_extension script to catch AttributeError instead of InterfaceError.
- Update expected test outputs to reflect updated behavior

Enhancements:
- Bump container images and tool versions across Snakemake rules and environment definitions.
- Consolidate and modernize Conda environments by removing environment.root.yml and streamlining dependency specifications.

Build:
- Consolidate environment files into a standalone development environment configuration
- Update Conda environments to use latest dependency versions

CI:
- Switch all CI jobs to Miniforge with strict channel priorities and unified environment.dev.yml.
- Add a dedicated Apptainer integration test job alongside Conda and local workflow tests.

Tests:
- Rename and adapt test scripts to use Apptainer flags and simplify rule graph generation.

Documentation:
- Replace Singularity references with Apptainer in README and config docs and update setup and test instructions.

Linked issue #151: ci: run singularity test
The CI Singularity test fails apparently due to the lack of privilege permissions and for now it is not included in the CI test. 

Previous unsuccessful approaches were:

1. Setting a `$CONDA_PREFIX`  to see if that helps on creating the container
2. Upgrade the `python` and `singularity` versions separately and at the same time
3. Adding the `--fakeroot` argument to singularity to see if that way I could have root permissions
4. Use `singularity` as a `docker`  image
5. Set manual permissions using `setfacl` (GitHub Actions did not find the path even if it is the one in the log files)

Some new approaches are:

1. Use `apptainer` instead
2. Use the [snakemake-github-action](https://github.com/snakemake/snakemake-github-action)


In addition, the pipeline outputs the same content but in different in-file order (see [this comment](https://github.com/zavolanlab/mirflowz/pull/150#issuecomment-2717930652) ) when using Conda or Singularity
Linked issue comments: user: login: uniqueg
created_at: 2025-04-22T03:47:29Z
body: In ZARP, [this](https://github.com/zavolanlab/zarp/pull/181) is what actually helped me address this issue.

Basically:
- use Apptainer
- do not install Apptainer via Conda, but use a dedicated GitHub Action

Plus updates of:
- Ubuntu, Python and Snakemake versions
- Docs
- and several other things

But these last changes may not be required in _MIRFLOWZ_ - or at least not all of them...
author_association: MEMBER

Diff:
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index f866dee4..b01590d6 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -7,7 +7,7 @@ on:
   push:
     branches:
       - '*'
- 
+
 jobs:
 
   static-code-analysis:
@@ -15,26 +15,23 @@ jobs:
     defaults:
       run:
         shell: bash -l {0}
-
     steps:
-      - name: check out repository
+
+      - name: Checkout MIRFLOWZ repository
         uses: actions/checkout@v4
-         
-      - name: Setup Conda/Mamba
+
+      - name: Setup Miniforge with MIRFLOWZ environment
         uses: conda-incubator/setup-miniconda@v3
         with:
           activate-environment: mirflowz
           auto-activate-base: false
-          auto-update-conda: false
-          channel-priority: true
+          auto-update-conda: true
+          channel-priority: strict
           conda-remove-defaults: true
-          environment-file: environment.yml
-          mamba-version: "2"
-      
-      - name: Update mirflowz env with dev packages
-        run: mamba env update -n mirflowz -f environment.dev.yml  
-      
-      - name: display environment info
+          environment-file: environment.dev.yml
+          miniforge-version: latest
+
+      - name: Display Conda and environment info
         run: |
           conda info -a
           conda list
@@ -60,77 +57,102 @@ jobs:
     defaults:
       run:
         shell: bash -l {0}
-  
+
     steps:
-    
-      - name: check out repository
+
+      - name: Checkout MIRFLOWZ repository
         uses: actions/checkout@v4
-         
-      - name: setup Conda/Mamba
+
+      - name: Setup Miniforge with MIRFLOWZ environment
         uses: conda-incubator/setup-miniconda@v3
         with:
           activate-environment: mirflowz
           auto-activate-base: false
-          auto-update-conda: false
-          channel-priority: true
+          auto-update-conda: true
+          channel-priority: strict
           conda-remove-defaults: true
-          environment-file: environment.yml
-          mamba-version: "2"
-      
-      - name: update mirflowz env with dev packages
-        run: mamba env update -n mirflowz -f environment.dev.yml 
-      
-      - name: display environment info
+          environment-file: environment.dev.yml
+          miniforge-version: latest
+
+      - name: Display Conda and environment info
         run: |
           conda info -a
-          conda list   
+          conda list
 
-      - name: run test for snakemake format
+      - name: Run test script for snakemake format
         run: bash test/test_snakefmt.sh
 
-      - name: run test for snakemate lint
-        run: bash test/test_snakemake_lint.sh 
+      - name: Run test script for snakemate lint
+        run: bash test/test_snakemake_lint.sh
 
-      - name: run test for rule graph
+      - name: Run test script for rule graph
         run: bash test/test_rule_graph.sh
 
 
-  snakemake-integration-test:
+  snakemake-apptainer-integration-test:
     runs-on: ubuntu-latest
     defaults:
       run:
         shell: bash -l {0}
-  
     steps:
-    
-      - name: check out repository
+
+      - name: Checkout MIRFLOWZ repository
         uses: actions/checkout@v4
-         
-      - name: setup Conda/Mamba
+
+      - name: Setup Miniforge with MIRFLOWZ environment
         uses: conda-incubator/setup-miniconda@v3
         with:
           activate-environment: mirflowz
           auto-activate-base: false
-          auto-update-conda: false
-          channel-priority: true
+          auto-update-conda: true
+          channel-priority: strict
           conda-remove-defaults: true
           environment-file: environment.yml
-          mamba-version: "2"
-      
-      - name: update mirflowz env with root packages
-        run: |
-          mamba env update -n mirflowz -f environment.root.yml 
-          mamba install -c conda-forge mamba=1
-      
-      - name: display environment info
+          miniforge-version: latest
+
+      - name: Install Apptainer
+        uses: eWaterCycle/setup-apptainer@v2
+        with:
+          apptainer-version: 1.3.6
+
+      - name: Display Conda and environment info
         run: |
           conda info -a
-          conda list   
+          conda list
+
+      - name: Run Apptainer local test script
+        run: bash test/test_workflow_local_with_apptainer.sh
+
 
-      - name: run local test with Conda
+  snakemake-conda-integration-test:
+    runs-on: ubuntu-latest
+    defaults:
+      run:
+        shell: bash -l {0}
+
+    steps:
+
+      - name: Checkout MIRFLOWZ repository
+        uses: actions/checkout@v4
+
+      - name: Setup Miniforge with MIRFLOWZ environment
+        uses: conda-incubator/setup-miniconda@v3
+        with:
+          activate-environment: mirflowz
+          auto-activate-base: false
+          auto-update-conda: true
+          channel-priority: strict
+          conda-remove-defaults: true
+          environment-file: environment.yml
+          miniforge-version: latest
+
+      - name: Display Conda and environment info
         run: |
-          conda config --set channel_priority strict
-          bash test/test_workflow_local_with_conda.sh
+          conda info -a
+          conda list
+
+      - name: Run Conda local test script
+        run: bash test/test_workflow_local_with_conda.sh
 
 
   unit-testing:
@@ -140,29 +162,26 @@ jobs:
         shell: bash -l {0}
 
     steps:
-    
-      - name: checkout repository
+
+      - name: Checkout MIRFLOWZ repository
         uses: actions/checkout@v4
-         
-      - name: setup Conda/Mamba
+
+      - name: Setup Miniforge with MIRFLOWZ environment
         uses: conda-incubator/setup-miniconda@v3
         with:
           activate-environment: mirflowz
           auto-activate-base: false
-          auto-update-conda: false
-          channel-priority: true
+          auto-update-conda: true
+          channel-priority: strict
           conda-remove-defaults: true
-          environment-file: environment.yml
-          mamba-version: "2"
-      
-      - name: update mirflowz env with dev packages
-        run: mamba env update -n mirflowz -f environment.dev.yml 
-      
-      - name: display environment info
+          environment-file: environment.dev.yml
+          miniforge-version: latest
+
+      - name: Display Conda and environment info
         run: |
           conda info -a
           conda list
-      
-      - name: run unit tests
+
+      - name: Run unit tests
         working-directory: ./scripts/tests
         run: pytest --import-mode prepend --cov=scripts --cov-branch --cov-report=term-missing
diff --git a/.snakemake-workflow-catalog.yml b/.snakemake-workflow-catalog.yml
index 4ae567aa..bd526896 100644
--- a/.snakemake-workflow-catalog.yml
+++ b/.snakemake-workflow-catalog.yml
@@ -1,5 +1,5 @@
 usage:
   software-stack-deployment:
     conda: true
-    singularity: true
+    apptainer: true
   report: true
diff --git a/README.md b/README.md
index 68a49f68..93abab23 100644
--- a/README.md
+++ b/README.md
@@ -43,9 +43,9 @@ cd mirflowz
 For improved reproducibility and reusability of the workflow, as well as an
 easy means to run it on a high performance computing (HPC) cluster managed,
 e.g., by [Slurm][slurm], all steps of the workflow run inside isolated
-environments ([Singularity][singularity] containers or [Conda][conda]
+environments ([Apptainer][apptainer] containers or [Conda][conda]
 environments). As a consequence, running this workflow has only a few
-individual dependencies. These are managed by the package manager Conda, which 
+individual dependencies. These are managed by the package manager Conda, which
 needs to be installed on your system before proceeding.
 
 If you do not already have Conda installed globally on your system,
@@ -71,25 +71,11 @@ HPC cluster.
 conda config --set channel_priority strict
 ```
 
-If you plan to run _MIRFLOWZ_ via Singularity and do not already
-have it installed globally on your system, you must further update the Conda
-environment using the `environment.root.yml` with the command below.
-Mind that you must have the environment activated to update it.
+If you would like to contribute to _MIRFLOWZ_ development, you may find it
+useful to create your environment with the development dependencies:
 
 ```bash
-conda env update -f environment.root.yml
-```
-
-> Note that you will need to have root permissions on your system to be able
-> to install Singularity. If you want to run _MIRFLOWZ_ on an HPC cluster
-> (recommended in almost all cases), ask your systems administrator about
-> Singularity.
-
-If you would like to contribute to _MIRFLOWZ_ development, you may find it 
-useful to further update your environment with the development dependencies:
-
-```bash
-conda env update -f environment.dev.yml
+conda env create -f environment.dev.yml
 ```
 
 ### Testing your installation
@@ -102,10 +88,10 @@ the instructions in this section to make sure the workflow is ready to use.
 Execute one of the following commands to run the test workflow on your local
 machine:
 
-- Test workflow on local machine with **Singularity**:
+- Test workflow on local machine with **Apptainer**:
 
 ```bash
-bash test/test_workflow_local_with_singularity.sh
+bash test/test_workflow_local_with_apptainer.sh
 ```
 
 - Test workflow on local machine with **Conda**:
@@ -114,15 +100,15 @@ bash test/test_workflow_local_with_singularity.sh
 bash test/test_workflow_local_with_conda.sh
 ```
 
-#### Run test workflow on a cluster via SLURM
+#### Run test workflow on a cluster via Slurm
 
 Execute one of the following commands to run the test workflow on a
-slurm-managed high-performance computing (HPC) cluster:
+Slurm-managed high-performance computing (HPC) cluster:
 
-- Test workflow with **Singularity**:
+- Test workflow with **Apptainer**:
 
 ```bash
-bash test/test_workflow_slurm_with_singularity.sh
+bash test/test_workflow_slurm_with_apptainer.sh
 ```
 
 
@@ -135,7 +121,7 @@ bash test/test_workflow_slurm_with_conda.sh
 #### Rule graph
 
 Execute the following command to generate a rule graph image for the workflow.
-The output will be found in the `images/` directory in the repository root. 
+The output will be found in the `images/` directory in the repository root.
 
 ```bash
 bash test/test_rule_graph.sh
@@ -158,22 +144,22 @@ bash test/test_cleanup.sh
 Now that your virtual environment is set up and the workflow is deployed and
 tested, you can go ahead and run the workflow on your samples.
 
-### Preparing inputs 
+### Preparing inputs
 
-It is suggested to have all the input files for a given run (or hard links 
-pointing to them) inside a dedicated directory, for instance under the 
+It is suggested to have all the input files for a given run (or hard links
+pointing to them) inside a dedicated directory, for instance under the
 _MIRFLOWZ_ root directory. This way, it is easier to keep the data together,
-set up Singularity access to them and reproduce analyses.  
+set up Apptainer access to them and reproduce analyses.
 
 #### 1. Prepare a sample table
 
-Refer to `test/test_files/sample_table.tsv` to know what this file 
-must look like, or use it as a template. 
+Refer to `test/test_files/sample_table.tsv` to know what this file
+must look like, or use it as a template.
 
 ```bash
 touch path/to/your/sample/table.tsv
 ```
-> Fill the sample table according to the following requirements:  
+> Fill the sample table according to the following requirements:
 >
 > - `sample`. Arbitrary name for the miRNA sequencing library.
 > - `sample_file`. Path to the miRNA sequencing library file. The path must be
@@ -184,7 +170,7 @@ touch path/to/your/sample/table.tsv
 
 #### 2. Prepare genome resources
 
-There are 4 files you must provide: 
+There are 4 files you must provide:
 
 1. A **`gzip`ped FASTA** file containing **reference sequences**, typically the
    genome of the source/organism from which the library was extracted.
@@ -209,9 +195,8 @@ There are 4 files you must provide:
    reference names** used in the miRNA annotation file (column 1; "UCSC style")
    and in the gene annotations and reference sequence files (column 2; "Ensembl
    style"). Values in column 1 are expected to be unique, no header is
-   expected, and any additional columns will be ignored. [This
-   resource][chrMap] provides such files for various organisms, and in the
-   expected format.
+   expected, and any additional columns will be ignored. [This resource][chrMap]
+   provides such files for various organisms, and in the expected format.
 
 5. **OPTIONAL**: A **BED6** file with regions for which to produce
    [ASCII-style pileups][ascii-pileups]. If not provided, no pileups are
@@ -233,29 +218,43 @@ cp  config/config_template.yaml  path/to/config.yaml
 
 Open the new copy in your editor of choice and adjust the configuration
 parameters to your liking. The template explains what each of the
-parameters mean and how you can meaningfully adjust them. 
+parameters mean and how you can meaningfully adjust them.
 
 ### Running the workflow
 
 With all the required files in place, you can now run the workflow locally
-via Singularity with the following command:  
+via Apptainer with the following command:
+
+```bash
+snakemake \
+    --snakefile="path/to/Snakefile" \
+    --cores 4  \
+    --configfile="path/to/config.yaml" \
+    --software-deployment-method apptainer \
+    --apptainer-args "--bind ${PWD}/../" \
+    --printshellcmds \
+    --rerun-incomplete \
+    --verbose
+```
+
+Likewise, you can run the workflow locally via Conda with the following
+command:
 
 ```bash
 snakemake \
     --snakefile="path/to/Snakefile" \
     --cores 4  \
     --configfile="path/to/config.yaml" \
-    --use-singularity \
-    --singularity-args "--bind ${PWD}/../" \
+    --software-deployment-method conda \
     --printshellcmds \
     --rerun-incomplete \
     --verbose
 ```
 
-> **NOTE:** Depending on your working directory, you do not need to use the 
+> **NOTE:** Depending on your working directory, you do not need to use the
 > parameters `--snakefile` and `--configfile`. For instance, if the `Snakefile`
 > is in the same directory or the `workflow/` directory is beneath the current
-> working directory, there's no need for the `--snakefile` directory. Refer to 
+> working directory, there's no need for the `--snakefile` directory. Refer to
 > the [Snakemake documentation][snakemakeDocu] for more information.
 
 After successful execution of the workflow, results and logs will be found in
@@ -299,7 +298,7 @@ snakemake \
     --snakefile="path/to/Snakefile" \
     --cores 4  \
     --configfile="path/to/config.yaml" \
-    --use-conda \
+    --software-deployment-method conda \
     --printshellcmds \
     --rerun-incomplete \
     --no-hooks \
@@ -323,7 +322,7 @@ snakemake \
 ```
 
 > **NOTE:** The report creation must be done after running the workflow in
-> order to have the runtime statistics and the results. 
+> order to have the runtime statistics and the results.
 
 ## Workflow description
 
@@ -382,7 +381,7 @@ into genomic ones and alignments are combined into a single file. Duplicate
 alignments resulting from the partially redundant mapping strategy are
 discarded and only the best alignments for each read are retained (_i.e._ the
 ones with the smallest edit distance). In addition, and due to the alignment's
-aggregation, a second filtering according to the new NH is performed. 
+aggregation, a second filtering according to the new NH is performed.
 If a read has been aligned beyond a specified threshold, it is removed due to
 (1) performance reasons as the file size can rapidly increase, and (2) the fact
 that each read contributes to each count `1/N` where `N` is the number of
@@ -460,11 +459,12 @@ For questions or suggestions regarding the code, please use the
 
 &copy; 2023 [Zavolab, Biozentrum, University of Basel][zavolab]
 
+[apptainer]: <https://apptainer.org/docs/user/main/index.html>
 [ascii-pileups]: <https://git.scicore.unibas.ch/zavolan_group/tools/ascii-alignment-pileup>
 [bed-format]: <https://gist.github.com/deliaBlue/19ad3740c95937378bd9281bd9d1bc72>
 [chrMap]: <https://github.com/dpryan79/ChromosomeMappings>
-[conda]: <https://docs.conda.io/projects/conda/en/latest/index.html>
 [cluster execution]: <https://snakemake.readthedocs.io/en/stable/executing/cluster.html>
+[conda]: <https://docs.conda.io/projects/conda/en/latest/index.html>
 [email]: <zavolab-biozentrum@unibas.ch>
 [ensembl]: <https://ensembl.org/>
 [issue-tracker]: <https://github.com/zavolanlab/mirflowz/issues>
@@ -472,7 +472,6 @@ For questions or suggestions regarding the code, please use the
 [miniconda-installation]: <https://docs.conda.io/en/latest/miniconda.html>
 [mirbase]: <https://mirbase.org/>
 [rule-graph]: images/rule_graph.svg
-[singularity]: <https://apptainer.org/admin-docs/3.8/index.html>
 [slurm]: <https://slurm.schedmd.com/documentation.html>
 [snakemake]: <https://snakemake.readthedocs.io/en/stable/>
 [snakemakeDocu]: <https://snakemake.readthedocs.io/en/stable/executing/cli.html>
diff --git a/config/README.md b/config/README.md
index 1d896c5c..5743b401 100644
--- a/config/README.md
+++ b/config/README.md
@@ -18,24 +18,9 @@ conda config --set channel_priority strict
 
 For a faster creation of the environment (and Conda environments in general),
 you can also install [Mamba][mamba] on top of Conda. In that case, replace
-`conda` with `mamba` in the commands above (particularly in 
+`conda` with `mamba` in the commands above (particularly in
 `conda env create`).
 
-## Running _MIRFLOWZ_ with Singularity
-
-If you want to run _MIRFLOWZ_ via Singularity and do not already
-have it installed globally on your system, you must further update the Conda
-environment with:
-
-```bash
-conda env update -f environment.root.yml
-```
-
-> Mind that you must have the environment activated and root permissions on
-> your system to install Singularity. If you want to run _MIRFLOWZ_ on an HPC
-> cluster (recommended in almost all cases), ask your system administrator
-> about Singularity.
-
 # Run the workflow on your own samples
 
 In order to run _MIRFLOWZ_ on your own samples, we recommend having all the
@@ -59,7 +44,7 @@ table must look like or use it as a template.
 touch samples.tsv
 ```
 
-> Fill the sample table according to the following requirements:  
+> Fill the sample table according to the following requirements:
 >
 > - `sample`. Arbitrary name for the miRNA sequencing library.
 > - `sample_file`. Path to the miRNA sequencing library file. The path must be
@@ -70,7 +55,7 @@ touch samples.tsv
 
 ## 2. Prepare the genome resources
 
-There are 4 files you must provide: 
+There are 4 files you must provide:
 
 1. A **`gzip`ped FASTA** file containing **reference sequences**, typically the
    genome of the source/organism from which the library was extracted.
diff --git a/config/config_template.yaml b/config/config_template.yaml
index 4c4f2eb0..d502a955 100644
--- a/config/config_template.yaml
+++ b/config/config_template.yaml
@@ -15,12 +15,12 @@ samples: path/to/samples_table.tsv
 genome_file: path/to/gzipped/ensembl/genome.fa.gz
 gtf_file: path/to/gzipped/ensembl/gene_annotations.gtf.gz
 mirna_file: path/to/unzipped/mirbase/mirna_annotations.gff3
-bed_file: path/to/pileups/genomic_regions.bed
+bed_file: path/to/pileups/pileup_regions.bed
 
 # Tab-separated mappings table between UCSC (column 1)
-# and Ensembl (coulm 2) chromosome names 
+# and Ensembl (column 2) chromosome names 
 # Available at: https://github.com/dpryan79/ChromosomeMappings
-map_chr_file: path/to/ucsc_ensembl_mappings.tsv
+map_chr_file: path/to/ucsc_to_ensembl.tsv
 
 
 ###############################
diff --git a/environment.dev.yml b/environment.dev.yml
index ee918bc7..bed7269c 100644
--- a/environment.dev.yml
+++ b/environment.dev.yml
@@ -1,17 +1,17 @@
 name: mirflowz
-channels:
-  - conda-forge
-  - bioconda
-dependencies: 
-  - black>=24.1.1
-  - coverage>=7.3.2
-  - flake8>=6.1.0
+dependencies:
+  - bioconda::gffutils>=0.13
+  - bioconda::snakefmt>=0.11
+  - bioconda::snakemake>=8
+  - black>=24.1
+  - coverage>=7.8
+  - flake8>=7.2
   - flake8-docstrings>=1.7.0
-  - gffutils>=0.12
-  - mypy>=1.6.1
-  - pylint>=3.0.1
-  - pysam>=0.22.0
-  - pytest>=7.4.2
-  - pytest-cov
-  - snakefmt
-  - snakemake=7.24.0
+  - graphviz
+  - mypy>=1.15
+  - pulp
+  - pylint>=3.3.7
+  - pysam>=0.23
+  - pytest>=8.3.5
+  - pytest-cov>=6.1.1
+  - python>=3.12
diff --git a/environment.root.yml b/environment.root.yml
deleted file mode 100644
index e5c33b35..00000000
--- a/environment.root.yml
+++ /dev/null
@@ -1,5 +0,0 @@
-name: mirflowz
-channels:
-  - bioconda
-dependencies:
-  - singularity>=3.5.2
diff --git a/environment.yml b/environment.yml
index 70d28d4d..fc473a13 100644
--- a/environment.yml
+++ b/environment.yml
@@ -1,9 +1,6 @@
 name: mirflowz
-channels:
-  - conda-forge
-  - bioconda
 dependencies:
-  - graphviz=7.1.0
-  - python=3.9.16
-  - snakemake=7.24.0
-  - pulp=2.7.0
+  - graphviz
+  - python>=3.12
+  - bioconda::snakemake>=8
+  - pulp
diff --git a/scripts/mirna_extension.py b/scripts/mirna_extension.py
index 7ba749a4..51e8dcc6 100755
--- a/scripts/mirna_extension.py
+++ b/scripts/mirna_extension.py
@@ -3,7 +3,6 @@
 
 import argparse
 from pathlib import Path
-from sqlite3 import InterfaceError
 from typing import Optional
 
 import gffutils  # type: ignore
@@ -41,7 +40,7 @@ def set_db(self, path: Path) -> None:
             )
         except gffutils.exceptions.EmptyInputError:
             pass
-        except InterfaceError as err:
+        except AttributeError as err:
             raise AnnotationException(
                 "\n\n"
                 "Illegal coordinate: The provided GFF3 miRNA annotation file"
@@ -257,11 +256,13 @@ def write_gff(
             if not isinstance(self.db_out, gffutils.FeatureDB):
                 _file.write("")
             elif feature_type is None:
-                for feature in self.db_out.all_features():  # type: ignore
+                for feature in self.db_out.all_features(  # type: ignore
+                    order_by=("featuretype", "strand", "start", "end")
+                ):
                     _file.write(str(feature) + "\n")
             else:
                 for feature in self.db_out.features_of_type(  # type: ignore
-                    feature_type
+                    feature_type, order_by=("strand", "start", "end")
                 ):
                     _file.write(str(feature) + "\n")
 
diff --git a/scripts/oligomap_output_to_sam_nh_filtered.py b/scripts/oligomap_output_to_sam_nh_filtered.py
index dcb60a90..a652df7c 100755
--- a/scripts/oligomap_output_to_sam_nh_filtered.py
+++ b/scripts/oligomap_output_to_sam_nh_filtered.py
@@ -213,7 +213,7 @@ def get_cigar_md(
         else:
             idx = bars_line.index(" ")
             cigarStr = f"{idx}M{indelerr}{bars_line.count('|') - idx}M"
-            matchingString = f"MD:Z:{idx}^{ref_seq[idx]}{seq_len - idx -1}"
+            matchingString = f"MD:Z:{idx}^{ref_seq[idx]}{seq_len - idx - 1}"
 
         return cigarStr, matchingString
 
diff --git a/scripts/tests/files/mir_anno.gff3 b/scripts/tests/files/mir_anno.gff3
index 7d6f99d4..52d83b36 100644
--- a/scripts/tests/files/mir_anno.gff3
+++ b/scripts/tests/files/mir_anno.gff3
@@ -1,6 +1,6 @@
-19	.	miRNA_primary_transcript	121034	121104	.	+	.	ID=MI0000779;Alias=MI0000779;Name=hsa-mir-371a_-1_+3
 19	.	miRNA	121034	121065	.	+	.	ID=MIMAT0004687;Alias=MIMAT0004687;Name=hsa-miR-371a-5p;Derives_from=MI0000779
 19	.	miRNA	121070	121104	.	+	.	ID=MIMAT0000723;Alias=MIMAT0000723;Name=hsa-miR-371a-3p;Derives_from=MI0000779
-19	.	miRNA_primary_transcript	121032	121102	.	-	.	ID=MI0017393;Alias=MI0017393;Name=hsa-mir-371b_-5_+0
-19	.	miRNA	121069	121102	.	-	.	ID=MIMAT0019892;Alias=MIMAT0019892;Name=hsa-miR-371b-5p;Derives_from=MI0017393
 19	.	miRNA	121032	121066	.	-	.	ID=MIMAT0019893;Alias=MIMAT0019893;Name=hsa-miR-371b-3p;Derives_from=MI0017393
+19	.	miRNA	121069	121102	.	-	.	ID=MIMAT0019892;Alias=MIMAT0019892;Name=hsa-miR-371b-5p;Derives_from=MI0017393
+19	.	miRNA_primary_transcript	121034	121104	.	+	.	ID=MI0000779;Alias=MI0000779;Name=hsa-mir-371a_-1_+3
+19	.	miRNA_primary_transcript	121032	121102	.	-	.	ID=MI0017393;Alias=MI0017393;Name=hsa-mir-371b_-5_+0
diff --git a/test/expected_output.md5 b/test/expected_output.md5
index d11c1295..662dcae3 100644
--- a/test/expected_output.md5
+++ b/test/expected_output.md5
@@ -1,51 +1,51 @@
 68f943f89b52d628851dd97fb1399d68  results/TABLES/all_mirna_counts.tab
 363ecee318c57ee7e2e45ca468007baa  results/TABLES/all_pri-mir_counts.tab
-316efffbd8b0cfc2969cd4c5ee057b89  results/test_lib/alignments_intersecting_mirna_uncollapsed_sorted.bam.bai
-29cf04bd111832d5d9d26f9c1a9894ea  results/test_lib/alignments_intersecting_mirna_uncollapsed_sorted.bam
-0b6863e021b54b78f2f40058b27eaa3b  results/test_lib/alignments_intersecting_mirna.sam
-6a7f2ae6d84ed57628967544330fc872  results/test_lib/alignments_intersecting_primir.sam
+f5e880a6384a1b1f1131610e49a0a831  results/test_lib/alignments_intersecting_mirna_uncollapsed_sorted.bam.bai
+2610dfba2a30ba803b8ef88b34b5a168  results/test_lib/alignments_intersecting_mirna_uncollapsed_sorted.bam
+dae531a85a1bc27f0b809c97c132f3f0  results/test_lib/alignments_intersecting_mirna.sam
+69c318b04001f254894fedb57b7b611b  results/test_lib/alignments_intersecting_primir.sam
 eec9be6cda61d2728290c92c1209f455  results/intermediates/TABLES/mirna_counts_test_lib
 a844e3a29159e36e2f17a0646d1e8c5f  results/intermediates/TABLES/pri-mir_counts_test_lib
-253e7a10388ff4a18d5f3f8028004da2  results/intermediates/test_lib/mappings_all.sam
-8dada05fe4dbfc956d467d52f11644e3  results/intermediates/test_lib/mappings_all_removed_inferiors.sam
-d3848af1f2a343941e12b725d7d37873  results/intermediates/test_lib/alignments_intersecting_primir.bam
-7d0d83a3106163fcfdb801209888cbc4  results/intermediates/test_lib/segemehl_transcriptome_mappings.sam
-7d0d83a3106163fcfdb801209888cbc4  results/intermediates/test_lib/transcriptome_mappings_filtered_nh.sam
-3b8091000d4c786f8871d650f4c907e4  results/intermediates/test_lib/alignments_all.bam
-14da2f122b2d252508296ff4ad83051e  results/intermediates/test_lib/mappings_all_no_header.sam
-f412f1f63cae895ec17aa7fec2658d64  results/intermediates/test_lib/alignments_all.sam
-c6dfe3a55de3113a3c994d872a5a9bbd  results/intermediates/test_lib/oligomap_genome_mappings.oligomap
-43fbcf0d44425cb6e876b8e2cd777aa0  results/intermediates/test_lib/alignments_intersecting_mirna_tag.sam
-cbca4cbf95c3e5b159fa0d80696f6e0d  results/intermediates/test_lib/segemehl_genome_mappings.sam
+48e605df55bf2dd37ea5a5a74eb5872a  results/intermediates/test_lib/mappings_all.sam
+47793583f3433fa312007fa37709d93a  results/intermediates/test_lib/mappings_all_removed_inferiors.sam
+377a6c15c3188b47a5666a8c451986f8  results/intermediates/test_lib/alignments_intersecting_primir.bam
+e632f8984d423d46bbb377ec75468521  results/intermediates/test_lib/segemehl_transcriptome_mappings.sam
+e632f8984d423d46bbb377ec75468521  results/intermediates/test_lib/transcriptome_mappings_filtered_nh.sam
+017ee7623a76031f99ab50c6fc37cd4d  results/intermediates/test_lib/alignments_all.bam
+a124a5afdb5f7bfbcc5683260556c9c4  results/intermediates/test_lib/mappings_all_no_header.sam
+fad20f19e232895ca2dbe9907fdb707a  results/intermediates/test_lib/alignments_all.sam
+defdc8c46e1d73692edde0e0278f2d5e  results/intermediates/test_lib/oligomap_genome_mappings.oligomap
+08dc4c4afd678960b64951f94ec40ab6  results/intermediates/test_lib/alignments_intersecting_mirna_tag.sam
+76643f87bb2e2bff77d1b1223d7720b5  results/intermediates/test_lib/segemehl_genome_mappings.sam
 d41d8cd98f00b204e9800998ecf8427e  results/intermediates/test_lib/transcriptome_mappings_to_genome.sam
 d41d8cd98f00b204e9800998ecf8427e  results/intermediates/test_lib/oligomap_transcriptome_mappings.oligomap
 63a32839360a985b68e0685aafad5c54  results/intermediates/test_lib/fa/reads.fa
-e24f1f663f801a90009e1bdf628a7d21  results/intermediates/test_lib/alignments_intersecting_mirna_uncollapsed.sam
+fb9c073d8da661f46930ec95d75a4458  results/intermediates/test_lib/alignments_intersecting_mirna_uncollapsed.sam
 edcb854702519c0002d8ce89a21e54ef  results/intermediates/test_lib/reads_formatted.fasta
-c6ef2e5f154ea021c8664f1cc02d7d35  results/intermediates/test_lib/intersected_extended_mirna.bed
-e2f20dfbdc561519996957905db85e29  results/intermediates/test_lib/alignments_all_sorted_test_lib.bam
+1a547487b8e92ad85bb26ff9b1db1f93  results/intermediates/test_lib/intersected_extended_mirna.bed
+75e621af043ab39ad17faa243d518a2c  results/intermediates/test_lib/alignments_all_sorted_test_lib.bam
 ec0e9bcc8ea857da897035c8fca4078f  results/intermediates/test_lib/reads_trimmed_adapters.fasta
-74c423b0ef8e3fd50f81f5db212653b8  results/intermediates/test_lib/alignments_intersecting_mirna_uncollapsed.bam
-6398390952e3818993015e669b1153ac  results/intermediates/test_lib/oligomap_genome_sorted.oligomap
-745cedc730468523d979a9616b0d29ba  results/intermediates/test_lib/genome_mappings.sam
-a4fdacc067c22d66f14904dd20a6dc2f  results/intermediates/test_lib/oligomap_genome_report.txt
-d698f919c9b3e3978ad0f525614f4918  results/intermediates/test_lib/transcriptome_mappings_no_header.sam
-7d0d83a3106163fcfdb801209888cbc4  results/intermediates/test_lib/transcriptome_mappings.sam
-475079d157603b80b9eff995e7086174  results/intermediates/test_lib/oligomap_genome_mappings.sam
-b0fd0e87c245148de24f21d05509c86f  results/intermediates/test_lib/alignments_intersecting_mirna_sorted_tag.sam
-2abfc2d8430fdd7ce833f709d6b2d5c6  results/intermediates/test_lib/alignments_intersecting_primir_sorted.bam
+e44c86c2f8a96e63d080eb065f69e8f3  results/intermediates/test_lib/alignments_intersecting_mirna_uncollapsed.bam
+36f7d024fe6ddfd3e788aebf61c61061  results/intermediates/test_lib/oligomap_genome_sorted.oligomap
+1f1b873d05ec14ef9b16376a1c98315b  results/intermediates/test_lib/genome_mappings.sam
+f5cb65466d328036a15b66cfbd4d8419  results/intermediates/test_lib/oligomap_genome_report.txt
+6cbdb9299e09b3e39b79a50db69226b5  results/intermediates/test_lib/transcriptome_mappings_no_header.sam
+e632f8984d423d46bbb377ec75468521  results/intermediates/test_lib/transcriptome_mappings.sam
+947607be69c16246f8dc9adbd9b971c8  results/intermediates/test_lib/oligomap_genome_mappings.sam
+d47f954fd49863c62f81c2dd8fa37e06  results/intermediates/test_lib/alignments_intersecting_mirna_sorted_tag.sam
+63f2fb37ff10bd748ec72649eec37ecf  results/intermediates/test_lib/alignments_intersecting_primir_sorted.bam
 d41d8cd98f00b204e9800998ecf8427e  results/intermediates/test_lib/oligomap_transcriptome_mappings.sam
-14da2f122b2d252508296ff4ad83051e  results/intermediates/test_lib/genome_mappings_no_header.sam
-745cedc730468523d979a9616b0d29ba  results/intermediates/test_lib/genome_mappings_filtered_nh.sam
-e9c89b6e415dbd02e25f77eb2794f72d  results/intermediates/test_lib/mappings_all_sorted_by_id.sam
-00c964896380dd69220a920b68d52240  results/intermediates/test_lib/reads_collapsed.fasta
-b47c031fdbb74e7f7c47f014173dc042  results/intermediates/test_lib/intersected_extended_primir.bed
-ebfee11f638c4a9c8cbc1c373ee21570  results/intermediates/test_lib/reads_filtered_for_oligomap.fasta
-f7474afd318cedc01112e8aea809ec58  results/intermediates/test_lib/oligomap_transcriptome_report.txt
+a124a5afdb5f7bfbcc5683260556c9c4  results/intermediates/test_lib/genome_mappings_no_header.sam
+1f1b873d05ec14ef9b16376a1c98315b  results/intermediates/test_lib/genome_mappings_filtered_nh.sam
+dbcd73dd7275f31cdbfd63250e79ebb4  results/intermediates/test_lib/mappings_all_sorted_by_id.sam
+2c77ffa021dda190d82f3f54a3312393  results/intermediates/test_lib/reads_collapsed.fasta
+f68693cfaa1e6ea78e1a5562ade6d9ed  results/intermediates/test_lib/intersected_extended_primir.bed
+c2a5770a755ada66ef63d96eec4afb00  results/intermediates/test_lib/reads_filtered_for_oligomap.fasta
+fe5388094985e9604a302d39d2abc82c  results/intermediates/test_lib/oligomap_transcriptome_report.txt
 d41d8cd98f00b204e9800998ecf8427e  results/intermediates/test_lib/oligomap_transcriptome_sorted.oligomap
 96a178aabc41d3020992efd6acbecb5a  results/intermediates/genome_processed.fa.bz
 d34fc868b861b1bc46db07a397dc0f10  results/intermediates/genome_processed.fa.bz.fai
-e5400bfa476fc387ff858ab4dbfd11e3  results/intermediates/extended_mirna_annotation_6_nt.gff3
+508af0ee1a82234159a403d119bf6318  results/intermediates/extended_mirna_annotation_6_nt.gff3
 8148cd880602255be166beb59bbed95a  results/intermediates/genome_header.sam
 09e24a504bfec37fee3d5ff1b5c7738e  results/intermediates/exons.bed
 4fb453846e88593d0cac13220ec2d685  results/intermediates/segemehl_genome_index.idx
@@ -54,7 +54,7 @@ f5749f5d2096386444a3d021e0a01817  results/intermediates/genome_processed.fa.bz.g
 d34fc868b861b1bc46db07a397dc0f10  results/intermediates/genome_processed.fa.fai
 21e102e4ebd3508bb06f46366a3d578d  results/intermediates/exons.gtf
 003b92b245ac336e3d70a513033e1cee  results/intermediates/transcriptome_trimmed_id.fa
-8fc8e961a10b73ab7901c406a0679634  results/intermediates/extended_primir_annotation_6_nt.gff3
+235ecb5152586df7091d8451b484157e  results/intermediates/extended_primir_annotation_6_nt.gff3
 f28cc0143ab6659bef3de3a7afa1dccc  results/intermediates/mirna_annotations.gff3
 2d437f8681f4248d4f2075f86debb920  results/intermediates/transcriptome.fa
 7eb64c112830266bcf416ded60b4cf77  results/intermediates/segemehl_transcriptome_index.idx
diff --git a/test/test_rule_graph.sh b/test/test_rule_graph.sh
index b0e475b0..62d6c6ee 100755
--- a/test/test_rule_graph.sh
+++ b/test/test_rule_graph.sh
@@ -21,7 +21,6 @@ snakemake \
     --snakefile="../workflow/Snakefile" \
     --configfile="config.yaml" \
     --rulegraph \
-    --printshellcmds \
     --dryrun \
-    --verbose \
-    | dot -Tsvg > "../images/rule_graph.svg"
+    --quiet="all" \
+| dot -Tsvg > "../images/rule_graph.svg"
diff --git a/test/test_workflow_local_with_singularity.sh b/test/test_workflow_local_with_apptainer.sh
similarity index 93%
rename from test/test_workflow_local_with_singularity.sh
rename to test/test_workflow_local_with_apptainer.sh
index b8f3deaf..4d9e8ec1 100755
--- a/test/test_workflow_local_with_singularity.sh
+++ b/test/test_workflow_local_with_apptainer.sh
@@ -22,8 +22,8 @@ snakemake \
     --snakefile="../workflow/Snakefile" \
     --cores 4  \
     --configfile="config.yaml" \
-    --use-singularity \
-    --singularity-args "--bind ${PWD}/../" \
+    --software-deployment-method apptainer \
+    --apptainer-args "--bind ${PWD}/../" \
     --printshellcmds \
     --rerun-incomplete \
     --no-hooks \
diff --git a/test/test_workflow_local_with_conda.sh b/test/test_workflow_local_with_conda.sh
index 110c7412..63e8ad67 100755
--- a/test/test_workflow_local_with_conda.sh
+++ b/test/test_workflow_local_with_conda.sh
@@ -22,17 +22,17 @@ snakemake \
     --snakefile="../workflow/Snakefile" \
     --cores 4  \
     --configfile="config.yaml" \
-    --use-conda \
+    --software-deployment-method conda \
     --printshellcmds \
     --rerun-incomplete \
     --no-hooks \
     --verbose
 
 # Snakemake report
-#snakemake \
-#    --snakefile="../workflow/Snakefile" \
-#    --configfile="config.yaml" \
-#    --report="snakemake_report.html"
+snakemake \
+    --snakefile="../workflow/Snakefile" \
+    --configfile="config.yaml" \
+    --report="snakemake_report.html"
 
 # Check md5 sum of some output files
 find results/ -type f -name \*\.gz -exec gunzip '{}' \;
diff --git a/test/test_workflow_slurm_with_singularity.sh b/test/test_workflow_slurm_with_apptainer.sh
similarity index 95%
rename from test/test_workflow_slurm_with_singularity.sh
rename to test/test_workflow_slurm_with_apptainer.sh
index 88781fda..a6c7ba90 100755
--- a/test/test_workflow_slurm_with_singularity.sh
+++ b/test/test_workflow_slurm_with_apptainer.sh
@@ -38,8 +38,8 @@ snakemake \
         --open-mode=append" \
     --jobscript="../jobscript.sh" \
     --jobs=20 \
-    --use-singularity \
-    --singularity-args="--bind ${PWD}/../" \
+    --software-deployment-method apptainer \
+    --apptainer-args="--bind ${PWD}/../" \
     --printshellcmds \
     --rerun-incomplete \
     --no-hooks \
diff --git a/test/test_workflow_slurm_with_conda.sh b/test/test_workflow_slurm_with_conda.sh
index 2e6c7154..ee5b316f 100755
--- a/test/test_workflow_slurm_with_conda.sh
+++ b/test/test_workflow_slurm_with_conda.sh
@@ -38,7 +38,7 @@ snakemake \
         --open-mode=append" \
     --jobscript="../jobscript.sh" \
     --jobs=20 \
-    --use-conda \
+    --software-deployment-method conda \
     --printshellcmds \
     --rerun-incomplete \
     --no-hooks \
diff --git a/workflow/envs/bedtools.yaml b/workflow/envs/bedtools.yaml
index bb636507..0cfb818a 100644
--- a/workflow/envs/bedtools.yaml
+++ b/workflow/envs/bedtools.yaml
@@ -2,5 +2,5 @@
 channels:
   - bioconda
 dependencies:
-  - bedtools=2.30
-...
\ No newline at end of file
+  - bedtools=2.31.1
+...
diff --git a/workflow/envs/cutadapt.yaml b/workflow/envs/cutadapt.yaml
index 4ed4c54c..3319694d 100644
--- a/workflow/envs/cutadapt.yaml
+++ b/workflow/envs/cutadapt.yaml
@@ -3,5 +3,5 @@ channels:
   - conda-forge
   - bioconda
 dependencies:
-  - cutadapt=4.3
-...
\ No newline at end of file
+  - cutadapt=5.0
+...
diff --git a/workflow/envs/fastx_toolkit.yaml b/workflow/envs/fastx_toolkit.yaml
index 1427c428..3142e6c1 100644
--- a/workflow/envs/fastx_toolkit.yaml
+++ b/workflow/envs/fastx_toolkit.yaml
@@ -1,6 +1,7 @@
 ---
 channels:
+  - conda-forge
   - bioconda
 dependencies:
-  - fastx_toolkit=0.0.14
+  - fastx_toolkit=0.0.14=h503566f_13
 ...
diff --git a/workflow/envs/gffread.yaml b/workflow/envs/gffread.yaml
deleted file mode 100644
index 44fd8529..00000000
--- a/workflow/envs/gffread.yaml
+++ /dev/null
@@ -1,6 +0,0 @@
----
-channels:
-  - bioconda
-dependencies:
-  - gffread=0.12.1
-...
\ No newline at end of file
diff --git a/workflow/envs/gffutils.yaml b/workflow/envs/gffutils.yaml
index fcc11086..3d40ee8c 100644
--- a/workflow/envs/gffutils.yaml
+++ b/workflow/envs/gffutils.yaml
@@ -3,5 +3,5 @@ channels:
   - conda-forge
   - bioconda
 dependencies:
-  - gffutils=0.12
+  - gffutils=0.13
 ...
diff --git a/workflow/envs/pysam.yaml b/workflow/envs/pysam.yaml
index 9d184698..ae4107b0 100644
--- a/workflow/envs/pysam.yaml
+++ b/workflow/envs/pysam.yaml
@@ -3,5 +3,5 @@ channels:
   - conda-forge
   - bioconda
 dependencies:
-  - pysam=0.20.0
-...
\ No newline at end of file
+  - pysam=0.23.0
+...
diff --git a/workflow/envs/python.yaml b/workflow/envs/python.yaml
index b419efe0..773d8fab 100644
--- a/workflow/envs/python.yaml
+++ b/workflow/envs/python.yaml
@@ -2,5 +2,5 @@
 channels:
   - conda-forge
 dependencies:
-  - python>=3.9.16
-...
\ No newline at end of file
+  - python>=3.11
+...
diff --git a/workflow/envs/samtools.yaml b/workflow/envs/samtools.yaml
index 2ed2b06a..fb1689e0 100644
--- a/workflow/envs/samtools.yaml
+++ b/workflow/envs/samtools.yaml
@@ -3,5 +3,5 @@ channels:
   - conda-forge
   - bioconda
 dependencies:
-  - samtools=1.16.1
-...
\ No newline at end of file
+  - samtools=1.21
+...
diff --git a/workflow/envs/segemehl.yaml b/workflow/envs/segemehl.yaml
index 5e5be112..195d4c53 100644
--- a/workflow/envs/segemehl.yaml
+++ b/workflow/envs/segemehl.yaml
@@ -1,6 +1,4 @@
 ---
-channels:
-  - bioconda
 dependencies:
-  - segemehl=0.3.4
-...
\ No newline at end of file
+  - bioconda/label/cf201901::segemehl
+...
diff --git a/workflow/rules/map.smk b/workflow/rules/map.smk
index b1d68d6a..0d227901 100644
--- a/workflow/rules/map.smk
+++ b/workflow/rules/map.smk
@@ -115,7 +115,7 @@ rule fastq_quality_filter:
     log:
         LOCAL_LOG / "fastq_quality_filter_{sample}.log",
     container:
-        "docker://quay.io/biocontainers/fastx_toolkit:0.0.14--h87f3376_10"
+        "docker://quay.io/biocontainers/fastx_toolkit:0.0.14--h503566f_13"
     conda:
         ENV_DIR / "fastx_toolkit.yaml"
     shell:
@@ -143,7 +143,7 @@ rule fastq_to_fasta:
     log:
         LOCAL_LOG / "fastq_to_fasta_{sample}.log",
     container:
-        "docker://quay.io/biocontainers/fastx_toolkit:0.0.14--h87f3376_10"
+        "docker://quay.io/biocontainers/fastx_toolkit:0.0.14--h503566f_13"
     conda:
         ENV_DIR / "fastx_toolkit.yaml"
     shell:
@@ -168,7 +168,7 @@ rule format_fasta:
     log:
         LOCAL_LOG / "format_fasta_{sample}.log",
     container:
-        "docker://quay.io/biocontainers/fastx_toolkit:0.0.14--h87f3376_10"
+        "docker://quay.io/biocontainers/fastx_toolkit:0.0.14--h503566f_13"
     conda:
         ENV_DIR / "fastx_toolkit.yaml"
     shell:
@@ -197,7 +197,7 @@ rule remove_adapters:
     resources:
         threads=8,
     container:
-        "docker://quay.io/biocontainers/cutadapt:4.3--py310h1425a21_0"
+        "docker://quay.io/biocontainers/cutadapt:5.0--py39hbcbf7aa_0"
     conda:
         ENV_DIR / "cutadapt.yaml"
     shell:
@@ -227,7 +227,7 @@ rule collapse_identical_reads:
     log:
         LOCAL_LOG / "collapse_identical_reads_{sample}.log",
     container:
-        "docker://quay.io/biocontainers/fastx_toolkit:0.0.14--h87f3376_10"
+        "docker://quay.io/biocontainers/fastx_toolkit:0.0.14--h503566f_13"
     conda:
         ENV_DIR / "fastx_toolkit.yaml"
     shell:
@@ -322,7 +322,7 @@ rule filter_fasta_for_oligomap:
     log:
         LOCAL_LOG / "filter_fasta_for_oligomap_{sample}.log",
     container:
-        "docker://python:3.9.16"
+        "docker://python:3.11.12"
     conda:
         ENV_DIR / "python.yaml"
     shell:
@@ -414,7 +414,7 @@ rule convert_genome_to_sam_oligomap:
         time=1,
         queue=1,
     container:
-        "docker://python:3.9.16"
+        "docker://python:3.11.12"
     conda:
         ENV_DIR / "python.yaml"
     shell:
@@ -502,7 +502,7 @@ rule convert_transcriptome_to_sam_oligomap:
     log:
         LOCAL_LOG / "oligomap_transcriptome_to_sam_{sample}.log",
     container:
-        "docker://python:3.9.16"
+        "docker://python:3.11.12"
     conda:
         ENV_DIR / "python.yaml"
     shell:
@@ -571,7 +571,7 @@ rule filter_genome_by_nh:
     log:
         LOCAL_LOG / "filter_genome_by_nh_{sample}.log",
     container:
-        "docker://quay.io/biocontainers/pysam:0.15.2--py38h7be0bb8_11"
+        "docker://quay.io/biocontainers/pysam:0.23.0--py39hdd5828d_0"
     conda:
         ENV_DIR / "pysam.yaml"
     shell:
@@ -599,7 +599,7 @@ rule filter_transcriptome_by_nh:
     log:
         LOCAL_LOG / "filter_transcriptome_by_nh_{sample}.log",
     container:
-        "docker://quay.io/biocontainers/pysam:0.15.2--py38h7be0bb8_11"
+        "docker://quay.io/biocontainers/pysam:0.23.0--py39hdd5828d_0"
     conda:
         ENV_DIR / "pysam.yaml"
     shell:
@@ -625,7 +625,7 @@ rule remove_header_genome_mappings:
     log:
         LOCAL_LOG / "remove_header_genome_mappings_{sample}.log",
     container:
-        "docker://quay.io/biocontainers/samtools:1.16.1--h00cdaf9_2"
+        "docker://quay.io/biocontainers/samtools:1.21--h96c455f_1"
     conda:
         ENV_DIR / "samtools.yaml"
     shell:
@@ -647,7 +647,7 @@ rule remove_header_transcriptome_mappings:
     log:
         LOCAL_LOG / "remove_header_transcriptome_mappings_{sample}.log",
     container:
-        "docker://quay.io/biocontainers/samtools:1.16.1--h00cdaf9_2"
+        "docker://quay.io/biocontainers/samtools:1.21--h96c455f_1"
     conda:
         ENV_DIR / "samtools.yaml"
     shell:
@@ -671,7 +671,7 @@ rule transcriptome_to_genome_maps:
     log:
         LOCAL_LOG / "transcriptome_to_genome_maps_{sample}.log",
     container:
-        "docker://perl:5.37.10"
+        "docker://perl:5.40.2"
     conda:
         ENV_DIR / "perl.yaml"
     shell:
@@ -739,7 +739,7 @@ rule sort_maps_by_id:
     log:
         LOCAL_LOG / "sort_maps_by_id_{sample}.log",
     container:
-        "docker://quay.io/biocontainers/samtools:1.16.1--h00cdaf9_2"
+        "docker://quay.io/biocontainers/samtools:1.21--h96c455f_1"
     conda:
         ENV_DIR / "samtools.yaml"
     shell:
@@ -765,7 +765,7 @@ rule remove_inferiors:
         mem=15,
         threads=4,
     container:
-        "docker://perl:5.37.10"
+        "docker://perl:5.40.2"
     conda:
         ENV_DIR / "perl.yaml"
     shell:
@@ -796,7 +796,7 @@ rule filter_by_indels:
         mem=15,
         threads=4,
     container:
-        "docker://quay.io/biocontainers/pysam:0.15.2--py38h7be0bb8_11"
+        "docker://quay.io/biocontainers/pysam:0.23.0--py39hdd5828d_0"
     conda:
         ENV_DIR / "pysam.yaml"
     shell:
@@ -822,7 +822,7 @@ rule convert_all_alns_sam_to_bam:
     log:
         LOCAL_LOG / "convert_all_alns_sam_to_bam_{sample}.log",
     container:
-        "docker://quay.io/biocontainers/samtools:1.16.1--h00cdaf9_2"
+        "docker://quay.io/biocontainers/samtools:1.21--h96c455f_1"
     conda:
         ENV_DIR / "samtools.yaml"
     shell:
@@ -844,7 +844,7 @@ rule sort_all_alns_bam_by_position:
     log:
         LOCAL_LOG / "sort_all_alns_bam_by_position_{sample}.log",
     container:
-        "docker://quay.io/biocontainers/samtools:1.16.1--h00cdaf9_2"
+        "docker://quay.io/biocontainers/samtools:1.21--h96c455f_1"
     conda:
         ENV_DIR / "samtools.yaml"
     shell:
@@ -866,7 +866,7 @@ rule index_all_alns_bam:
     log:
         LOCAL_LOG / "index_all_alns_bam_{sample}.log",
     container:
-        "docker://quay.io/biocontainers/samtools:1.16.1--h00cdaf9_2"
+        "docker://quay.io/biocontainers/samtools:1.21--h96c455f_1"
     conda:
         ENV_DIR / "samtools.yaml"
     shell:
diff --git a/workflow/rules/pileup.smk b/workflow/rules/pileup.smk
index 31c56dd5..bab05d48 100644
--- a/workflow/rules/pileup.smk
+++ b/workflow/rules/pileup.smk
@@ -92,7 +92,7 @@ if config["bed_file"] == "":
         log:
             LOCAL_LOG / "create_empty_bed.log",
         container:
-            "docker://ubuntu:noble-20250127"
+            "docker://ubuntu:lunar-20221207"
         shell:
             "(touch {output})"
 
@@ -112,7 +112,7 @@ rule compress_reference_genome:
     log:
         LOCAL_LOG / "compress_reference_genome.log",
     container:
-        "docker://quay.io/biocontainers/samtools:1.16.1--h00cdaf9_2"
+        "docker://quay.io/biocontainers/samtools:1.21--h96c455f_1"
     conda:
         ENV_DIR / "samtools.yaml"
     shell:
diff --git a/workflow/rules/prepare.smk b/workflow/rules/prepare.smk
index d266d363..7aa81b02 100644
--- a/workflow/rules/prepare.smk
+++ b/workflow/rules/prepare.smk
@@ -250,7 +250,7 @@ rule create_genome_header:
     log:
         LOCAL_LOG / "create_genome_header.log",
     container:
-        "docker://quay.io/biocontainers/samtools:1.16.1--h00cdaf9_2"
+        "docker://quay.io/biocontainers/samtools:1.21--h96c455f_1"
     conda:
         ENV_DIR / "samtools.yaml"
     shell:
@@ -276,7 +276,7 @@ rule map_chr_names:
     log:
         LOCAL_LOG / "map_chr_names.log",
     container:
-        "docker://perl:5.37.10"
+        "docker://perl:5.40.2"
     conda:
         ENV_DIR / "perl.yaml"
     shell:
@@ -304,7 +304,7 @@ rule create_index_genome_fasta:
     log:
         LOCAL_LOG / "create_index_genome_fasta.log",
     container:
-        "docker://quay.io/biocontainers/samtools:1.16.1--h00cdaf9_2"
+        "docker://quay.io/biocontainers/samtools:1.21--h96c455f_1"
     conda:
         ENV_DIR / "samtools.yaml"
     shell:
@@ -357,7 +357,7 @@ rule extend_mirs_annotations:
     log:
         LOCAL_LOG / "extend_mirs_annotations.log",
     container:
-        "docker://quay.io/biocontainers/gffutils:0.11.1--pyh7cba7a3_0"
+        "docker://quay.io/biocontainers/gffutils:0.13--pyh7cba7a3_0"
     conda:
         ENV_DIR / "gffutils.yaml"
     shell:
diff --git a/workflow/rules/quantify.smk b/workflow/rules/quantify.smk
index e3fe7675..da0843cb 100644
--- a/workflow/rules/quantify.smk
+++ b/workflow/rules/quantify.smk
@@ -98,7 +98,7 @@ rule intersect_extended_primir:
     log:
         LOCAL_LOG / "intersect_extended_primir_{sample}.log",
     container:
-        "docker://quay.io/biocontainers/bedtools:2.30.0--h468198e_3"
+        "docker://quay.io/biocontainers/bedtools:2.31.1--h13024bc_3"
     conda:
         ENV_DIR / "bedtools.yaml"
     shell:
@@ -129,7 +129,7 @@ rule filter_sam_by_intersecting_primir:
     log:
         LOCAL_LOG / "filter_sam_by_intersecting_primir_{sample}.log",
     container:
-        "docker://quay.io/biocontainers/samtools:1.16.1--h00cdaf9_2"
+        "docker://quay.io/biocontainers/samtools:1.21--h96c455f_1"
     conda:
         ENV_DIR / "samtools.yaml"
     shell:
@@ -156,7 +156,7 @@ rule convert_intersecting_primir_sam_to_bam:
     log:
         LOCAL_LOG / "convert_intersecting_primir_sam_to_bam_{sample}.log",
     container:
-        "docker://quay.io/biocontainers/samtools:1.16.1--h00cdaf9_2"
+        "docker://quay.io/biocontainers/samtools:1.21--h96c455f_1"
     conda:
         ENV_DIR / "samtools.yaml"
     shell:
@@ -181,7 +181,7 @@ rule sort_intersecting_primir_bam_by_position:
     log:
         LOCAL_LOG / "sort_intersecting_primir_bam_by_position_{sample}.log",
     container:
-        "docker://quay.io/biocontainers/samtools:1.16.1--h00cdaf9_2"
+        "docker://quay.io/biocontainers/samtools:1.21--h96c455f_1"
     conda:
         ENV_DIR / "samtools.yaml"
     shell:
@@ -207,7 +207,7 @@ rule index_intersecting_primir_bam:
     log:
         LOCAL_LOG / "index_intersecting_primir_bam_{sample}.log",
     container:
-        "docker://quay.io/biocontainers/samtools:1.16.1--h00cdaf9_2"
+        "docker://quay.io/biocontainers/samtools:1.21--h96c455f_1"
     conda:
         ENV_DIR / "samtools.yaml"
     shell:
@@ -235,7 +235,7 @@ rule intersect_extended_mirna:
     log:
         LOCAL_LOG / "intersect_extended_mirna_{sample}.log",
     container:
-        "docker://quay.io/biocontainers/bedtools:2.30.0--h468198e_3"
+        "docker://quay.io/biocontainers/bedtools:2.31.1--h13024bc_3"
     conda:
         ENV_DIR / "bedtools.yaml"
     shell:
@@ -266,7 +266,7 @@ rule filter_sam_by_intersecting_mirna:
     log:
         LOCAL_LOG / "filter_sam_by_intersecting_mirna_{sample}.log",
     container:
-        "docker://quay.io/biocontainers/samtools:1.16.1--h00cdaf9_2"
+        "docker://quay.io/biocontainers/samtools:1.21--h96c455f_1"
     conda:
         ENV_DIR / "samtools.yaml"
     shell:
@@ -296,7 +296,7 @@ rule add_intersecting_mirna_tag:
     log:
         LOCAL_LOG / "add_intersecting_mirna_tag_{sample}.log",
     container:
-        "docker://quay.io/biocontainers/pysam:0.15.2--py38h7be0bb8_11"
+        "docker://quay.io/biocontainers/pysam:0.23.0--py39hdd5828d_0"
     conda:
         ENV_DIR / "pysam.yaml"
     shell:
@@ -325,7 +325,7 @@ rule sort_intersecting_mirna_by_feat_tag:
     log:
         LOCAL_LOG / "sort_intersecting_mirna_by_feat_tag_{sample}.log",
     container:
-        "docker://quay.io/biocontainers/samtools:1.16.1--h00cdaf9_2"
+        "docker://quay.io/biocontainers/samtools:1.21--h96c455f_1"
     conda:
         ENV_DIR / "samtools.yaml"
     shell:
@@ -353,7 +353,7 @@ rule quantify_mirna:
     log:
         LOCAL_LOG / "quantify_mirna_{sample}.log",
     container:
-        "docker://quay.io/biocontainers/pysam:0.20.0--py310hff46b53_0"
+        "docker://quay.io/biocontainers/pysam:0.23.0--py39hdd5828d_0"
     conda:
         ENV_DIR / "pysam.yaml"
     shell:
@@ -384,7 +384,7 @@ rule quantify_primir:
     log:
         LOCAL_LOG / "quantify_primir_{sample}.log",
     container:
-        "docker://quay.io/biocontainers/pysam:0.20.0--py310hff46b53_0"
+        "docker://quay.io/biocontainers/pysam:0.23.0--py39hdd5828d_0"
     conda:
         ENV_DIR / "pysam.yaml"
     shell:
@@ -450,7 +450,7 @@ rule uncollapse_reads:
     log:
         LOCAL_LOG / "uncollapse_reads_{sample}.log",
     container:
-        "docker://perl:5.37.10"
+        "docker://perl:5.40.2"
     conda:
         ENV_DIR / "perl.yaml"
     shell:
@@ -480,7 +480,7 @@ rule convert_uncollpased_reads_sam_to_bam:
     log:
         LOCAL_LOG / "convert_uncollapsed_reads_sam_to_bam_{sample}.log",
     container:
-        "docker://quay.io/biocontainers/samtools:1.16.1--h00cdaf9_2"
+        "docker://quay.io/biocontainers/samtools:1.21--h96c455f_1"
     conda:
         ENV_DIR / "samtools.yaml"
     shell:
@@ -506,7 +506,7 @@ rule sort_uncollpased_reads_bam_by_position:
     log:
         LOCAL_LOG / "sort_uncollapsed_reads_bam_by_position_{sample}.log",
     container:
-        "docker://quay.io/biocontainers/samtools:1.16.1--h00cdaf9_2"
+        "docker://quay.io/biocontainers/samtools:1.21--h96c455f_1"
     conda:
         ENV_DIR / "samtools.yaml"
     shell:
@@ -532,7 +532,7 @@ rule index_uncollapsed_reads_bam:
     log:
         LOCAL_LOG / "index_uncollapsed_reads_bam_{sample}.log",
     container:
-        "docker://quay.io/biocontainers/samtools:1.16.1--h00cdaf9_2"
+        "docker://quay.io/biocontainers/samtools:1.21--h96c455f_1"
     conda:
         ENV_DIR / "samtools.yaml"
     shell:


Reviews:
user: login: sourcery-ai[bot]
body: Hey @deliaBlue - I've reviewed your changes - here's some feedback:

- The GitHub Actions workflows have a lot of duplicated setup steps—consider using YAML anchors or a shared composite action to DRY up the CI definitions.
- With so many individual container and package version bumps, centralizing version variables or a shared configuration file could help ensure consistency and simplify future updates.
- There are leftover commented-out commands in the Apptainer test scripts; either remove them or add a brief explanation for why they are disabled.

<details>
<summary>Here's what I looked at during the review</summary>

- 🟢 **General issues**: all looks good
- 🟢 **Security**: all looks good
- 🟢 **Testing**: all looks good
- 🟢 **Documentation**: all looks good
</details>

***

<details>
<summary>Sourcery is free for open source - if you like our reviews please consider sharing them ✨</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20got%20an%20instant%20code%20review%20from%20%40SourceryAI%2C%20and%20it%20was%20brilliant%21%20It%27s%20free%20for%20open%20source%20and%20has%20a%20free%20trial%20for%20private%20code.%20Check%20it%20out%20https%3A//sourcery.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20got%20an%20instant%20code%20review%20from%20%40SourceryAI%2C%20and%20it%20was%20brilliant%21%20It%27s%20free%20for%20open%20source%20and%20has%20a%20free%20trial%20for%20private%20code.%20Check%20it%20out%20https%3A//sourcery.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https://sourcery.ai)
- [Facebook](https://www.facebook.com/sharer/sharer.php?u=https://sourcery.ai)

</details>

<sub>
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
</sub>
state: COMMENTED
author_association: NONE
submitted_at: 2025-05-12T12:48:04Z
user: login: sourcery-ai[bot]
body: Hey @deliaBlue - I've reviewed your changes and they look great!

<details>
<summary>Here's what I looked at during the review</summary>

- 🟡 **General issues**: 1 issue found
- 🟢 **Security**: all looks good
- 🟢 **Testing**: all looks good
- 🟢 **Documentation**: all looks good
</details>

***

<details>
<summary>Sourcery is free for open source - if you like our reviews please consider sharing them ✨</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20got%20an%20instant%20code%20review%20from%20%40SourceryAI%2C%20and%20it%20was%20brilliant%21%20It%27s%20free%20for%20open%20source%20and%20has%20a%20free%20trial%20for%20private%20code.%20Check%20it%20out%20https%3A//sourcery.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20got%20an%20instant%20code%20review%20from%20%40SourceryAI%2C%20and%20it%20was%20brilliant%21%20It%27s%20free%20for%20open%20source%20and%20has%20a%20free%20trial%20for%20private%20code.%20Check%20it%20out%20https%3A//sourcery.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https://sourcery.ai)
- [Facebook](https://www.facebook.com/sharer/sharer.php?u=https://sourcery.ai)

</details>

<sub>
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
</sub>
state: COMMENTED
author_association: NONE
submitted_at: 2025-05-12T12:49:27Z
user: login: sourcery-ai[bot]
body: Hey @deliaBlue - I've reviewed your changes - here's some feedback:

- Consider consolidating the repeated Miniforge setup steps in your GitHub Actions jobs into a reusable workflow or composite action to reduce duplication.
- Unify the Python version constraints across environment.yml and environment.dev.yml to avoid inconsistent runtime requirements.
- Centralize container image versions (e.g. via a config variable or rule parameter) instead of hard-coding them in every rule to simplify future version updates.

<details>
<summary>Here's what I looked at during the review</summary>

- 🟢 **General issues**: all looks good
- 🟢 **Security**: all looks good
- 🟢 **Testing**: all looks good
- 🟢 **Documentation**: all looks good
</details>

***

<details>
<summary>Sourcery is free for open source - if you like our reviews please consider sharing them ✨</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20got%20an%20instant%20code%20review%20from%20%40SourceryAI%2C%20and%20it%20was%20brilliant%21%20It%27s%20free%20for%20open%20source%20and%20has%20a%20free%20trial%20for%20private%20code.%20Check%20it%20out%20https%3A//sourcery.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20got%20an%20instant%20code%20review%20from%20%40SourceryAI%2C%20and%20it%20was%20brilliant%21%20It%27s%20free%20for%20open%20source%20and%20has%20a%20free%20trial%20for%20private%20code.%20Check%20it%20out%20https%3A//sourcery.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https://sourcery.ai)
- [Facebook](https://www.facebook.com/sharer/sharer.php?u=https://sourcery.ai)

</details>

<sub>
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
</sub>
state: COMMENTED
author_association: NONE
submitted_at: 2025-05-12T13:11:40Z
user: login: uniqueg
body: Great job!!

Looks mostly good to me. Just very few comments, shouldn't take long :)
state: CHANGES_REQUESTED
author_association: MEMBER
submitted_at: 2025-05-13T19:14:10Z
user: login: deliaBlue
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-14T17:48:41Z
user: login: uniqueg
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-19T08:43:26Z

Review comments:
diff_hunk: @@ -257,11 +256,13 @@ def write_gff(
             if not isinstance(self.db_out, gffutils.FeatureDB):
                 _file.write("")
             elif feature_type is None:
-                for feature in self.db_out.all_features():  # type: ignore
+                for feature in self.db_out.all_features(  # type: ignore
+                    order_by=("featuretype", "strand", "start")
+                ):
                     _file.write(str(feature) + "\n")
             else:
                 for feature in self.db_out.features_of_type(  # type: ignore
-                    feature_type
+                    feature_type, order_by=("strand", "start")
                 ):
                     _file.write(str(feature) + "\n")
path: scripts/mirna_extension.py
user: login: sourcery-ai[bot]
body: **suggestion (bug_risk):** Ordering by 'strand' and 'start' for features_of_type may not be stable if features share these attributes.

Ordering features by only 'strand' and 'start' isn't deterministic when they match. To enforce a stable order, add a third key (e.g., 'end' or 'id').

```suggestion
                for feature in self.db_out.features_of_type(  # type: ignore
                    feature_type, order_by=("strand", "start", "end")
                ):
```
created_at: 2025-05-12T12:49:27Z
author_association: NONE
diff_hunk: @@ -22,14 +22,14 @@ snakemake \
     --snakefile="../workflow/Snakefile" \
     --cores 4  \
     --configfile="config.yaml" \
-    --use-singularity \
-    --singularity-args "--bind ${PWD}/../" \
+    --use-apptainer \
+    --apptainer-args "--bind ${PWD}/../" \
     --printshellcmds \
     --rerun-incomplete \
     --no-hooks \
     --verbose
 
-# Snakemake report
+## Snakemake report
path: test/test_workflow_local_with_apptainer.sh
user: login: uniqueg
body: I can see why you did that, but then do it in all the test files :upside_down_face: :rofl: 
created_at: 2025-05-13T18:56:51Z
author_association: MEMBER
diff_hunk: @@ -233,29 +218,43 @@ cp  config/config_template.yaml  path/to/config.yaml
 
 Open the new copy in your editor of choice and adjust the configuration
 parameters to your liking. The template explains what each of the
-parameters mean and how you can meaningfully adjust them. 
+parameters mean and how you can meaningfully adjust them.
 
 ### Running the workflow
 
 With all the required files in place, you can now run the workflow locally
-via Singularity with the following command:  
+via Apptainer with the following command:
 
 ```bash
 snakemake \
     --snakefile="path/to/Snakefile" \
     --cores 4  \
     --configfile="path/to/config.yaml" \
-    --use-singularity \
-    --singularity-args "--bind ${PWD}/../" \
+    --use-apptainer \
+    --apptainer-args "--bind ${PWD}/../" \
path: README.md
user: login: uniqueg
body: Note that `--use-conda` and `--use-apptainer` are [deprecated](https://snakemake.readthedocs.io/en/stable/getting_started/migration.html#command-line-interface). Better to use `--software-deployment-method conda` and `--software-deployment-method apptainer` instead.
created_at: 2025-05-13T18:59:16Z
author_association: MEMBER
diff_hunk: @@ -22,14 +22,14 @@ snakemake \
     --snakefile="../workflow/Snakefile" \
     --cores 4  \
     --configfile="config.yaml" \
-    --use-singularity \
-    --singularity-args "--bind ${PWD}/../" \
+    --use-apptainer \
+    --apptainer-args "--bind ${PWD}/../" \
path: test/test_workflow_local_with_apptainer.sh
user: login: uniqueg
body: See comment about the use of `--use-conda` and `--use-apptainer` in `README.md`. If you use `--software-deployment-method conda` etc. instead, make sure you replace it everywhere.
created_at: 2025-05-13T18:59:57Z
author_association: MEMBER
path: workflow/envs/gffread.yaml
user: login: uniqueg
body: Was this just an orphan file? I don't see this changed in any of the SMK files.
created_at: 2025-05-13T19:03:35Z
author_association: MEMBER
path: scripts/tests/files/mir_anno.gff3
user: login: uniqueg
body: What's the rational for changing up the order here? I think it is convention (though not part of the specification) that GFF3 files are ordered by "parent feature 1 > child features for parent 1 > parent feature 2 > child features for parent 2 ...".

Did you change that up to ensure that your parser is immune to "random" sorting of features? Or do you now _expect_ this new order (child features first, then parent features)? If the former, okay, you can just close this command. If the latter, I think that would be potentially problematic.
created_at: 2025-05-13T19:10:20Z
author_association: MEMBER
path: workflow/envs/gffread.yaml
user: login: deliaBlue
body: Yeah, it was an orphan file.
created_at: 2025-05-14T17:48:41Z
author_association: COLLABORATOR

PR comments:
user: login: sourcery-ai[bot]
created_at: 2025-05-08T14:27:04Z
body: <!-- Generated by sourcery-ai[bot]: start review_guide -->

## Reviewer's Guide

This PR migrates the container runtime from Singularity to Apptainer, consolidates and modernizes Conda environment configurations, bumps container images and dependency versions across workflows, updates CI to use Miniforge with strict channel priorities and add an Apptainer integration test, revises documentation accordingly, and includes bug fixes in scripting and test renaming.

### File-Level Changes

| Change | Details | Files |
| ------ | ------- | ----- |
| Migrate CI workflows from Singularity to Apptainer | <ul><li>Replace --use-singularity flags with --use-apptainer and rename workflow jobs</li><li>Configure setup-miniconda to use strict channel priority and environment.dev.yml</li><li>Add a dedicated Apptainer integration test job alongside Conda and local workflow tests</li></ul> | `.github/workflows/tests.yml` |
| Replace Singularity references with Apptainer in documentation | <ul><li>Update README.md and config/README.md to mention Apptainer instead of Singularity</li><li>Adjust test command examples and environment instructions</li></ul> | `README.md`<br/>`config/README.md` |
| Consolidate and modernize Conda environment configurations | <ul><li>Remove environment.root.yml and merge into standalone environment.yml</li><li>Simplify environment.dev.yml and bump dependency versions</li><li>Use bioconda qualifiers and update Python/Snakemake versions</li></ul> | `environment.yml`<br/>`environment.dev.yml` |
| Bump container images and tool versions across Snakemake rules and environments | <ul><li>Update container tags for fastx_toolkit, cutadapt, pysam, samtools, bedtools, python, perl, gffutils, segemehl, etc.</li><li>Revise workflow/envs/*.yaml to new version specs</li></ul> | `workflow/rules/map.smk`<br/>`workflow/rules/quantify.smk`<br/>`workflow/rules/prepare.smk`<br/>`workflow/rules/pileup.smk`<br/>`workflow/envs/segemehl.yaml`<br/>`workflow/envs/bedtools.yaml`<br/>`workflow/envs/cutadapt.yaml`<br/>`workflow/envs/fastx_toolkit.yaml`<br/>`workflow/envs/pysam.yaml`<br/>`workflow/envs/python.yaml`<br/>`workflow/envs/samtools.yaml`<br/>`workflow/envs/gffutils.yaml` |
| Fix exception handling and feature ordering in mirna_extension script | <ul><li>Catch AttributeError instead of InterfaceError in database setup</li><li>Add order_by parameters to gffutils feature iteration</li></ul> | `scripts/mirna_extension.py` |
| Rename and adapt test scripts to use Apptainer and streamline rule graph generation | <ul><li>Rename Singularity test scripts to Apptainer variants</li><li>Update test_rule_graph.sh to use --quiet and remove printshellcmds</li><li>Adjust local and Slurm test scripts accordingly</li></ul> | `test/test_workflow_local_with_apptainer.sh`<br/>`test/test_workflow_slurm_with_apptainer.sh`<br/>`test/test_rule_graph.sh` |

### Assessment against linked issues

| Issue | Objective | Addressed | Explanation |
| ------ | ------- | ----- | ----- |
| #151 | The CI Singularity test fails due to lack of privilege permissions. | ✅ |  |
| #151 | The pipeline outputs the same content but in different in-file order when using Conda or Singularity. | ✅ |  |
| #152 | Ensure the names of test files in the config_template.yaml file match those in the test_files directory. | ✅ |  |

---

<details>
<summary>Tips and commands</summary>

#### Interacting with Sourcery

- **Trigger a new review:** Comment `@sourcery-ai review` on the pull request.
- **Continue discussions:** Reply directly to Sourcery's review comments.
- **Generate a GitHub issue from a review comment:** Ask Sourcery to create an
  issue from a review comment by replying to it. You can also reply to a
  review comment with `@sourcery-ai issue` to create an issue from it.
- **Generate a pull request title:** Write `@sourcery-ai` anywhere in the pull
  request title to generate a title at any time. You can also comment
  `@sourcery-ai title` on the pull request to (re-)generate the title at any time.
- **Generate a pull request summary:** Write `@sourcery-ai summary` anywhere in
  the pull request body to generate a PR summary at any time exactly where you
  want it. You can also comment `@sourcery-ai summary` on the pull request to
  (re-)generate the summary at any time.
- **Generate reviewer's guide:** Comment `@sourcery-ai guide` on the pull
  request to (re-)generate the reviewer's guide at any time.
- **Resolve all Sourcery comments:** Comment `@sourcery-ai resolve` on the
  pull request to resolve all Sourcery comments. Useful if you've already
  addressed all the comments and don't want to see them anymore.
- **Dismiss all Sourcery reviews:** Comment `@sourcery-ai dismiss` on the pull
  request to dismiss all existing Sourcery reviews. Especially useful if you
  want to start fresh with a new review - don't forget to comment
  `@sourcery-ai review` to trigger a new review!

#### Customizing Your Experience

Access your [dashboard](https://app.sourcery.ai) to:
- Enable or disable review features such as the Sourcery-generated pull request
  summary, the reviewer's guide, and others.
- Change the review language.
- Add, remove or edit custom review instructions.
- Adjust other review settings.

#### Getting Help

- [Contact our support team](mailto:support@sourcery.ai) for questions or feedback.
- Visit our [documentation](https://docs.sourcery.ai) for detailed guides and information.
- Keep in touch with the Sourcery team by following us on [X/Twitter](https://x.com/SourceryAI), [LinkedIn](https://www.linkedin.com/company/sourcery-ai/) or [GitHub](https://github.com/sourcery-ai).

</details>

<!-- Generated by sourcery-ai[bot]: end review_guide -->
author_association: NONE
user: login: deliaBlue
created_at: 2025-05-12T12:35:17Z
body: @sourcery-ai summary
author_association: COLLABORATOR
user: login: deliaBlue
created_at: 2025-05-12T12:40:54Z
body: @sourcery-ai review
author_association: COLLABORATOR
user: login: deliaBlue
created_at: 2025-05-12T12:48:40Z
body: @sourcery-ai guide
author_association: COLLABORATOR
user: login: deliaBlue
created_at: 2025-05-12T13:08:16Z
body: @sourcery-ai review
author_association: COLLABORATOR
user: login: uniqueg
created_at: 2025-05-13T18:48:11Z
body: > Hey @deliaBlue - I've reviewed your changes - here's some feedback:
> 
> * Consider consolidating the repeated Miniforge setup steps in your GitHub Actions jobs into a reusable workflow or composite action to reduce duplication.
> * Unify the Python version constraints across environment.yml and environment.dev.yml to avoid inconsistent runtime requirements.
> * Centralize container image versions (e.g. via a config variable or rule parameter) instead of hard-coding them in every rule to simplify future version updates.
> 
> Here's what I looked at during the review
> Sourcery is free for open source - if you like our reviews please consider sharing them ✨
> Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Those are actually pretty good suggestions. However, we won't address them - certainly not now.
author_association: MEMBER