Apptainer (a.k.a. Singularity)

Note

This documentation is a work in progress. Any comment or suggestion is welcome in sinfin@iac.es.

To stay informed about updates to "Apptainer", tips, etc., or to ask any questions regarding its use, please use the #computing/hpc channel in IAC-zulip.

From Introduction to Apptainer:

Apptainer is a container platform. It allows you to create and run containers that package up pieces of software in a way that is portable and reproducible. You can build a container using Apptainer on your laptop, and then run it on many of the largest HPC clusters in the world, local university or company clusters, a single server, in the cloud, or on a workstation down the hall. Your container is a single file, and you don’t have to worry about how to install all the software you need on each different operating system.

Here we only present a very brief introduction and some minimal examples to get you started with Apptainer at the IAC. For more detailed information, please visit the Apptainer webpage and refer to the official documentation.

Note

One important feature of Apptainer is that all our actions (from creating to running a container) can be performed with an unprivileged account, so you can run all the examples below with your regular IAC account.

Quick start

Attention

If you are using a "burro" with Slurm (all the public "burros" and some of the project ones), this will only work from within a Slurm session (either batch or interactive). More information about the use of the "burros" can be found here.

Note

This is an abridged version of the Quick Start page, shortened and modified to work with the IAC "burros".

At the IAC computers, the latest Apptainer version available at the time of writing is 1.4.0 and can be used via Environment Modules.

$ module load apptainer

Loaded environment for Apptainer version 1.4.0.

$ apptainer help

Linux container platform optimized for High Performance Computing (HPC) and
Enterprise Performance Computing (EPC)

[...]

Let's motivate the remaining of this section by assuming that, for some reason, we need to run version 0.5 of the Gnuastro package (https://www.gnu.org/software/gnuastro/). If we look at the Gnuastro versions page, we can see that this version was available in Ubuntu 18.04, so below we will show how to build and run a container with these versions.

There are different ways to create a container image, but often the most convenient way is to start with a basic image and then customize it with the software we need. So, let's start by downloading a pre-built Ubuntu 18.04 image. For this we can use Docker Hub.

Note

When downloading pre-built images proceed with caution and only do so from trusted providers. For example, below we use an image tagged as "Docker Official Image", which is built by the Docker Hub team.

In Docker Hub, we can find many pre-built Official Ubuntu images. Apptainer can download and convert these images to the required format with a single command:

$ apptainer pull docker://ubuntu:18.04
INFO:    Converting OCI blobs to SIF format
INFO:    Starting build...
[...]
INFO:    Creating SIF file...

In the directory where you run the previous command you should now have a ubuntu_18.04.sif file, which is the created container. We can verify that it indeed contains a Ubuntu 18.04 distribution with the apptainer exec command:

$ apptainer exec ubuntu_18.04.sif cat /etc/os-release
NAME="Ubuntu"
VERSION="18.04.6 LTS (Bionic Beaver)"
ID=ubuntu
[...]

In the example above we just run a command within the container, but we can also run a shell within the container and interact with it as if it were a virtual machine.

$ apptainer shell ubuntu_18.04.sif
Apptainer> cat /etc/os-release
NAME="Ubuntu"
VERSION="18.04.6 LTS (Bionic Beaver)"
[...]

Apptainer> which astfits
Apptainer> exit
$

Creating a customized container

As you can see in the example above, the pre-built image does not come with Gnuastro installed, so the astfits command is not found in the container. Thus, we will have to somehow modify the previous container or create a new one with the required packages installed. There are various ways to do this, but perhaps the simplest one is to follow these steps.

Create a sandbox container based on the pre-built image

A sandbox container is created as a directory instead of as a .sif file, and we just create it with the following command (after running it you will see that a custom_container directory has been created. You can obviously choose any name you want).

$ apptainer build --sandbox custom_container docker://ubuntu:18.04

Modify the container sandbox interactively

Then we can run a shell within the container, but this time we will do it with the option -f --writable, which will allow us to modify the container sandbox as if we were the root user (so that we can use administrative commands like apt-get to install software). As we can see below, we have no problem with installing and running Gnuastro this time.

$ apptainer shell -f --writable custom_container/
INFO:    User not listed in /etc/subuid, trying root-mapped namespace
INFO:    Using fakeroot command combined with root-mapped namespace
WARNING: Skipping mount /etc/localtime [binds]: /etc/localtime doesn't exist in container
Apptainer> apt-get update
[...]
Apptainer> apt-get install gnuastro
[...]
Apptainer> astfits --version
Fits (GNU Astronomy Utilities) 0.5

At this point we already have a customized container with Gnuastro installed, but not in a very convenient format. We could use Apptainer to convert the sandbox container to a ".sif" one, but this is not ideal either since if we did that, we would have a working container, but the information on how we created it would get lost. For reproducibility we show below how to rebuild this container via a "Definition File".

Rebuild the customized container with a Definition File

Being able to interactively modify the container as we did above is very useful when creating a new container to quickly find missing dependencies, check things are working correctly, etc., but once we have verified that the container is working as expected, it is extremely useful to write the necessary steps as a Definition File.

Tip

For anything not trivial, don't wait until you have a working container to write the Definition File. Instead incrementally write the file as you interact with the sandbox container, so that you don't forget any required step.

Definition files have a number of sections that are useful for more complex scenarios (see the official documentation), but in this case the definition file can be as simple as:

bootstrap: docker
from: ubuntu:18.04

%post
     :
     : ============================ :
     : CREATING Gnuastro CONTAINER  :
     : ============================ :

     apt-get update
     apt-get -y install gnuastro

     # To avoid the WARNING message about /etc/localtime
     touch /etc/localtime

Assuming we named the above file as custom.def, we can now rebuild and test our customized container with:

$ apptainer build custom.sif custom.def
[...]
INFO:    Creating SIF file...
INFO:    Build complete: custom.sif

$ apptainer exec custom.sif astfits --version
Fits (GNU Astronomy Utilities) 0.5
Copyright (C) 2015-2017, Free Software Foundation, Inc.
[...]

After we verify that the rebuilt container (named here custom.sif) does indeed work as expected, we can safely remove the whole sandbox directory created earlier. If we want, we can also safely rename the SIF file and/or move it to any other location.

If you want to rebuild the container in the future (or use it to build the container by somebody else or in some other machine), make sure to safely keep the ".def" file.

Using the applications inside the container

Other container technologies focus on isolation, but Apptainer favours integration over isolation, which means that a user is allowed to easily use host resources. For our example this means that the directory where we run the apptainer command is automatically shared with the container (if we need to mount other directories we can use the -B option of the apptainer exec command).

Thus, we can easily use the application inside the container, but using input and output files outside it. In the example below we use the astcrop command from the container (note that the SIF file does not need to be in the directory where we are calling the apptainer command), taking input data from the download directory and writing output data to the flat-ir directory (both relative to the directory where we are running the command).

$ apptainer exec ../custom.sif astcrop --mode=wcs -h0 \
     --output=flat-ir/xdf-f105w.fits \
     --polygon="53.187414,-27.779152 : 53.159507,-27.759633 : \
                53.134517,-27.787144 : 53.161906,-27.807208" \
     download/hlsp_xdf_hst_wfc3ir-60mas_hudf_f105w_v1_sci.fits

Crop started on Sat Dec 23 17:20:42 2023
  - Read metadata of 1 dataset.                        0.021913 seconds
  ---- flat-ir/xdf-f105w.fits         created: 1 input.
Crop finished in:  1.021145 seconds

Use cases

Running Python Code Across Different Systems Using Apptainer

Python virtual environments are widely used to manage dependencies for projects and they work great in your laptop or workstation. However, if you need to run the same project code in another machine (for example in one of the IAC "burros" or in the LaPalma supercomputer; or even submit hundreds of runs with HTCondor), these environments can be difficult to replicate due to: compatibility issues, dependency conflicts and lack of administrative privileges to install software.

In these cases Apptainer provides a simple and efficient solution to ensure the reproducibility of your code across different machines. As a basic example, let's imagine that we want to explore the famous Iris dataset for evaluating classification methods, and that we have developed our code using Python 3.8 and the packages requirements are given in the following requirements.txt file:

numpy==1.24.2
pandas==1.5.3
scikit-learn==1.2.2

Then, we can create the following iris.def file that will be used to create a container with the given Python version and requirement packages:

Bootstrap: docker
From: python:3.8-slim

%files
    $PWD/requirements.txt requirements.txt

%post
    pip install --no-cache-dir --root-user-action=ignore -r requirements.txt

    # Clean up
    pip cache purge
    apt-get clean
    rm -rf /var/lib/apt/lists/*
    rm -rf /usr/share/doc /usr/share/man /usr/share/info 

Creating the container is as simple as:

$ apptainer build iris.sif iris.def

and we have the full container with all the dependencies we need to run our code in a single file of just 130MB (obviously the container size will be different depending on the packages that you need to install).

This .sif file is all you need to copy to another computer in order to have your full environment, which you can then use to run your project code. So, for example, if your code is written in file iris.py, you could run this script (but using the environment in the iris.sif container, not the one in the host), with the apptainer run command. We illustrate this below for three different systems (a laptop with Manjaro Linux; an IAC "burro" with Ubuntu 22.04; and LaPalma Supercomputer with SLES12.2), emphasizing again that the iris.sif file is the same file:

angelv@siehp:$ head -n 1 /etc/os-release 
NAME="Manjaro Linux"

angelv@siehp:$ apptainer run iris.sif python iris.py
Dataset Overview:
Total samples: 150
Features: sepal length (cm), sepal width (cm), petal length (cm), petal width (cm)
Target classes: setosa, versicolor, virginica
[...]