Singularity and Apptainer
Apptainer is a fork of Singularity project, both projects are maintained
separately. We can use apptainer and singularity commands interchangeably.
Install Apptainer in RHEL based systems:
dnf install -y epel-release
dnf install -y apptainer
Install in Debian based systems:
sudo apt update && sudo apt install -y \
wget \
curl \
fuse3 \
libfuse3-3 \
uidmap \
squashfs-tools
# download latest version of apptainer
VER=$(curl -s https://api.github.com/repos/apptainer/apptainer/releases/latest | grep tag_name | cut -d '"' -f 4 | sed 's/v//')
wget https://github.com/apptainer/apptainer/releases/download/v${VER}/apptainer_${VER}_amd64.deb
sudo apt install -y ./apptainer_${VER}_amd64.deb
rm apptainer_${VER}_amd64.deb
Pull a docker image (it will convert and write to SIF format):
apptainer pull docker://nvcr.io/hpc/quantum_espresso:qe-7.3.1
Run a program via apptainer:
apptainer exec /qe-7.4.sif /opt/q-e-qe-7.4.1/bin/pw.x -in pw.in | tee pw.out
Bind a directory (some directories are mapped automatically, such as current working directory):
apptainer exec --bind /export:/export /qe-7.4.sif /opt/q-e-qe-7.4.1/bin/pw.x -in pw.in | tee pw.out
Launch apptainer via MPI:
mpirun -np 2 apptainer exec /qe-7.4.sif /opt/q-e-qe-7.4.1/bin/pw.x -in pw.in | tee pw.out
How to pass environment variable to the container:
- We can pass variable with
--envflag.
apptainer exec --env MY_VARIABLE="some_value" my-container.sif echo $MY_VARIABLE
- We can set variables with
APPTAINERENV_orSINGULARITYENV_prefix:
export APPTAINERENV_MY_VARIABLE="some_value"
apptainer exec my-container.sif env | grep MY_VARIABLE
Also there are APPTAINERENV_PREPEND_PATH and APPTAINERENV_APPEND_PATH to
modify PATH variable.
Example Apptainer definition file:
Bootstrap: docker
From: almalinux:9
%labels
Maintainer Pranab Das
Version QE-7.4.1
%environment
export OMP_NUM_THREADS=1
export PATH=/usr/lib64/openmpi/bin:$PATH
export LD_LIBRARY_PATH=/usr/lib64/openmpi/lib:$LD_LIBRARY_PATH
%post
dnf install -y epel-release
dnf config-manager --set-enabled crb
dnf groupinstall -y "Development Tools"
dnf install -y wget \
gcc \
gcc-c++ \
gcc-gfortran \
openblas-devel \
lapack-devel \
fftw-devel \
openmpi-devel \
scalapack-openmpi-devel \
libomp-devel
wget https://github.com/QEF/q-e/archive/refs/tags/qe-7.4.1.tar.gz
tar -xf qe-7.4.1.tar.gz
rm -f qe-7.4.1.tar.gz
cd q-e-qe-7.4.1
export OMP_NUM_THREADS=1
export PATH=/usr/lib64/openmpi/bin:$PATH
export LD_LIBRARY_PATH=/usr/lib64/openmpi/lib:$LD_LIBRARY_PATH
./configure CC=mpicc \
FC=mpifort \
F77=mpifort \
F90=mpifort \
MPIF90=mpif90 \
--enable-parallel \
--enable-openmp \
--with-scalapack=yes \
--prefix=/opt/qe-7.4.1
make all -j$(nproc)
make install
rm ..
rm -rf q-e-qe-7.4.1
Build container:
apptainer build espresso.sif espresso.def
Recover apptainer definition file from an image. Apptainer file is embedded into the SIF image.
apptainer exec <image_name.sif> cat /.singularity.d/Singularity
Sandbox mode
We can test commands interactively in sandbox mode. Create a sandbox with
--sandbox or -s:
apptainer build --sandbox test_sandbox/ docker://ubuntu:latest
This will create a standard directory named test_sandbox that contains full
Linux OS tree (/bin, /etc, /usr).
Now to install packages and save them to the sandbox folder, we need to enter
into the container in writable mode (use --writable or -w flag). We will
also need --fakeroot or -f flag to install software as root inside the
container:
apptainer shell --writable --fakeroot test_sandbox/
Inside the apptainer shell, we can install packages and experiment interactively, for example:
dnf install python3
We can package the sandbox directory into a SIF image:
apptainer build -f espresso.sif test_sandbox/
After the container is build and saved as SIF image, we may delete our sandbox folder. We need to set appropriate permission in order to be able to delete:
chmod -R u+rwX test_sandbox
rm -rf test_sandbox
Cleanup apptainer cache:
apptainer cache clean --force
Managing ENV variables
Apptainer stores ENV variables in severals files under /.singularity.d/env.
$ ls -l /.singularity.d/env
total 10
-rwxr-xr-x 1 root root 1337 Aug 1 01:32 01-base.sh
-rwxr-xr-x 1 root root 85 Aug 1 01:32 10-docker2singularity.sh
-rwxr-xr-x 1 root root 1707 Dec 6 06:18 90-environment.sh
-rwxr-xr-x 1 root root 0 Dec 6 06:09 94-appsbase.sh
-rwxr-xr-x 1 root root 3052 Aug 1 01:32 95-apps.sh
-rwxr-xr-x 1 root root 1568 Aug 1 01:32 99-base.sh
-rwxr-xr-x 1 root root 922 Aug 1 01:32 99-runtimevars.sh
Notice the numerical prefix, they are sourced in alphabetical order by the
shell. The ENV variables set in the %environment definition, goes to
90-environment.sh. This file is also symlinked from /environment:
$ apptainer exec base.sif ls -l /environment
lrwxrwxrwx 1 root root 36 Dec 6 13:29 /environment -> .singularity.d/env/90-environment.sh
However, above files are regenerated/
Such a file could be 91-environment.sh where we can write variables during the
build (%post section):
%post
echo 'export PATH=/test/path:$PATH' >> $APPTAINER_ENVIRONMENT
or,
%post
cat >> $APPTAINER_ENVIRONMENT <<'EOF'
export ONEAPI_ROOT=/opt/intel-2023.1
export TBBROOT=$ONEAPI_ROOT/tbb/2021.11
EOF
Notice the single quoted 'EOF', this prevents variable substitution in the
heredoc block.
We can inspect ENV variables with:
apptainer inspect --environment <image-name.sif>
apptainer exec <image-name.sif> printenv PATH
During build phase, if required, we can source variables with:
if [ -f /.singularity.d/env/91-environment.sh ]; then
. /.singularity.d/env/91-environment.sh
fi
or source all ENV files:
for script in /.singularity.d/env/*.sh; do
if [ -f "$script" ]; then
. "$script"
fi
done
Optionally, we can save variables in custom files, e.g., 91-oneapi.sh,
92-nvhpc.sh etc.
Managing SIF images with ORAS
Install ORAS in macos:
brew install oras
Login to GitHub Container Registry with ORAS (requires GitHub personal access token with sufficient permission):
echo $CR_PAT | oras login ghcr.io -u pranabdas --password-stdin
Push a file to GHCR:
oras push ghcr.io/pranabdas/drive:1.0.0 \
--artifact-type application/text \
./sample.txt
Fetch details about an object:
oras manifest fetch ghcr.io/pranabdas/drive:1.0.0
Print SHA-256-SUM of an object:
oras manifest fetch ghcr.io/pranabdas/drive:1.0.0 | jq '.layers[0].digest' | awk -F: '{print $2}'
Pull an object:
oras pull ghcr.io/pranabdas/drive:1.0.0
Pull and write object to a specific directory:
oras pull ghcr.io/pranabdas/drive:1.0.0 --output /opt
An ORAS image can be made associated with a repository by uploading to the repository namespace:
ghcr.io/pranabdas/<repository>/<image>:<tag>
We may need to specify per file media type <file-name>:<media-type> to set
artifact type correctly:
oras push ghcr.io/pranabdas/ubuntu.sif \
--artifact-type "application/vnd.sylabs.sif.layer.v1.sif" \
"qe.sif:application/vnd.sylabs.sif.layer.v1.sif"
Run apptainer in GitHub Codespaces
- Enable via docker-in-docker:
"features": {
"ghcr.io/devcontainers/features/docker-in-docker:2": {
"enableNonRootDocker": "true"
}
},
- If you do not need full docker-in-docker functionality, execute following commands to enable required permissions:
{
"name": "My Codespace",
"runArgs": [
"--cap-add=SYS_ADMIN",
"--security-opt", "apparmor=unconfined",
"--device=/dev/fuse",
"--security-opt", "systempaths=unconfined"
],
}
- Run
--privilegedmode (less secure than option 2):
{
"name": "My Codespace",
"runArgs": [
"--privileged"
],
}
Resources
- https://docs.sylabs.io/guides/latest/user-guide/
- https://apptainer.org/docs/user/main/index.html
- https://oras.land
- Singularity: Simple, secure containers for compute-driven workloads
- Apptainer Without Setuid
- Evaluation and Benchmarking of Singularity MPI containers on EU Research e-Infrastructure
- Singularity Containers Improve Reproducibility and Ease of Use in Computational Image Analysis Workflows
- Feasibility of Running Singularity Containers with Hybrid MPI on NASA High-End Computing Resources
- Singularity: Scientific containers for mobility of compute
- Enhancing reproducibility in scientific computing: Metrics and registry for Singularity containers