Introduction
Overview
Teaching: 10 min
Exercises: minQuestions
What issues motivated the creation of Apptainer/Singularity?
What are the differences between Docker, Apptainer and Singularity?
Objectives
Learn the design goals behind Apptainer/Singularity.
Working with containers
Containers are packages of software that encapsulates a system environment. An OS-level virtualization is delivered in a container, and any program running on it will use the contextualization isolated inside the container. They have the advantage that you can build a container on any system, your laptop for example, and then execute it anywhere as far as the platform compatible with the container is available.
Concepts such as reproducibility, preservation, and distribution are important in the HEP community, and the containers provide a solution totally compatible with such concepts:
- The version of some specific software used to perform an analysis can be preserved in a container with exactly the same environment used at that time.
- A “legacy software” with binaries only available for an outdated OS can be executed inside a container.
- All the necessary packages to process data can be easily distributed in containers, independently of the operating system available on the sites.
What about virtual machines?
Virtual Machines (VMs) provide the same isolation and reproducibility. However, they emulate the hardware, or at least the full OS, so they are computationally heavier to run, require bigger files when distributed and are less flexible than containers, that run only what you require to be different. All containers use the same OS Kernel of the host and contain only Libraries and the App that run in User space. See this article for a more detailed comparison. And if you are curious about the Linux Kernel mechanisms that make containers possible you can check this blog pos.
Why Apptainer/Singularity?
Many solutions are available to work with containers, for example Docker, one of the most popular platforms, or the free OSS Podman. However, the enterprise-based container frameworks were motivated to provide micro-services, a solution that fits well in the models of the industry, where system administrators with root privilege start the container engine daemon and install and run applications, each in its own container. This is not so compatible with the workflow in the High-Performance Computing (HPC) and High Throughput Computing (HTC), in which usually complex applications run exhaustively using all the available resources and without any special privilege.
Apptainer/Singularity is a container platform created for the HPC/HTC use case. It allows to build and run containers with just a few steps in most of the cases, and its design presents key advantages for the scientific community:
- Single-file based container images, facilitating the distribution, archiving and sharing.
- Ability to run, and in modern systems also to be installed, without any root daemon or setuid privileges. This makes it safer for large computer centers with shared resources.
- Preserves the permissions in the environment. The user outside the container can be the same user inside.
- Simple integration with resource managers and distributed computing frameworks because it runs as a regular application.
- Minimum overhead. No extra processes after initializing the container (Uses
execv()
).
Docker and Apptainer/Singularity
As you will learn in this training module, Apptainer/Singularity can be used with Docker images. If you want to learn more about Docker or Podman, check out our podman/docker training.
Apptainer vs Singularity
In these lessons you see the name Apptainer or Apptainer/Singularity, and the command apptainer
.
As stated in the move and renaming announcement, “Singularity IS Apptainer”.
Currently there are three products derived from the original Singularity project from 2015:
- SingularityPro: commercial software by Sylabs.
- SingularityCE: open source Singularity supported by Sylabs.
- Apptainer: open source Singularity, renamed in 2021 and hosted by the Linux Foundation. As of Spring 2024 all three Apptainer/Singularity versions are compatible and practically the same (differ in some additional format support), but have different roadmaps. There is hope that in the future they will join forces, but this is not currently the case. To understand how this came to be you can read the Singularity history on Wikipedia.
We are following Apptainer, the most adopted variation in the scientific community, so we are using the apptainer
command.
If you are using SingularityPro or SingularityCE, just replace the command apptainer
with singularity
and the
APPTAINER_
and APPTAINERENV_
variable prefixes with SINGULARITY_
and SINGULARITYENV_
.
If you have older scripts still using the singularity
command and SINGULARITY...
variables, they will work also in Apptainer because it is providing the singularity
alias
and full compatibility with the previous Singularity environment.
Documentation
The official Apptainer documentation is available online. Contains basic and advanced usage of Apptainer/Singularity beyond the scope of this training document. Take a look and read the nice introduction, explaining the motivation behind the creation of Apptainer/Singularity.
Key Points
Apptainer/Singularity is a container platform designed by and for scientists.
Single-file based container images facilitates the distribution.
Secure. User inside the container = user outside.
Containers and Images
Overview
Teaching: 20 min
Exercises: 5 minQuestions
How to pull Apptainer images from the libraries?
How to run commands inside the containers?
Objectives
Learn to search and pull images from the Sylabs Singularity library and Docker Hub.
Interact with the containers using the command-line interface.
The Apptainer Command Line Interface
Apptainer provides a command-line interface (CLI) to interact with the containers. You can search, build or run containers in a single line.
You can check the version of the Apptainer or Singularity command you are using with the --version
option:
singularity --version
# This works for both Singularity and Apptainer, which installs a link named `singularity` to maintain compatibility.
# In the future you may need to use `apptainer --version`
For this training we recommend Apptainer >= 1.0 or Singularity >= 3.5. Older versions may not have some of the features or behave differently. If you need to install or upgrade Apptainer/Singularity please refer to the Setup section. When asking for support please remember to include the version of Apptainer or Singularity being used, as in the output of the above command.
You can check the available options and subcommands using --help
:
apptainer --help
Downloading Images
Container Images are executables that bundle together all necessary components for an application or an environment, like a template for containers. Containers are the runtime instances of images — they are images with a state. CircleCI has a nice explanation of the differences.
Apptainer/Singularity can store, search and retrieve images in registries (searchable catalogs and repositories for images and containers). Images built by other users can be accessible using the CLI, can be pulled down, and become containers at runtime.
Sylabs, the developer of one Singularity flavor, hosts a public image registry, the Singularity Container Library where many user built images are available.
Apptainer, the Linux Foundation flavor, does not point by default to the Sylab registry via the Library API as previous versions did. You can change that running these commands (documented here):
apptainer remote add --no-login SylabsCloud cloud.sycloud.io
INFO: Remote "SylabsCloud" added.
apptainer remote use SylabsCloud
INFO: Remote "SylabsCloud" now in use.
apptainer remote list
Cloud Services Endpoints
========================
NAME URI ACTIVE GLOBAL EXCLUSIVE
DefaultRemote cloud.apptainer.org NO YES NO
SylabsCloud cloud.sycloud.io YES NO NO
...
Remote Endpoints, Library API and OCI Registries
Remotes are service endpoints Apptainer interacts with. These include Library API Registries, OCI Registries, and keyservers. The first two are used to search, pull and push images. The Library API,
library://
, was designed for SIF images, the Singularity Image Format. The Docker/ORAS API,docker://
, is used for Docker Hub, and other OCI (Open Containers Initiative) registries. These include Quay.io, NVIDIA NGC, the GitHub Container Registry, AWS ECR and many more.
Once you have setup a working registry you can use search and pull.
The command search
lists containers of interest
and shows information about users (owners or managers of stored containers) and collections (sets of containers).
For example:
# this command can take around a minute to complete
apptainer search centos7
No users found for 'centos7'
Found 1 collections for 'centos7'
library://shahzebmsiddiqui/easybuild-centos7
Found 15 containers for 'centos7'
library://gmk/default/centos7-devel
Tags: latest
...
Downloading an image from the Container Library is pretty straightforward:
apptainer pull library://gmk/default/centos7-devel
and the image is stored locally as a .sif
file (centos7-devel_latest.sif
, in this case).
Docker Images
Fortunately, Apptainer is also compatible with Docker images. There are many more registries with Docker images. Docker Hub is one of the largest libraries available, and any image hosted on the hub can be easily downloaded with the
docker://
URL as reference:apptainer pull docker://centos:centos7
Running Containers
There are several ways to interact with images and start containers. Here we will review how to initialize a shell environment and how to execute directly a command.
Initializing a shell and exiting it
The shell
command initializes a new interactive shell inside the container.
apptainer shell centos7-devel_latest.sif
Apptainer>
In this case, the container works as a lightweight virtual machine in which you can execute commands. Remember, inside the container you have the same user and permissions.
Apptainer> id
uid=1001(myuser) gid=1001(myuser) groups=1001(myuser),500(myothergroup)
Now quit the container by typing
Apptainer> exit
or hitting Ctrl + D
.
Note that when exiting from the Apptainer image all the running processes are killed (stopped).
Changes saved into bound directories are preserved. By default anything else in the container is lost (we’ll see later about writable images).
Bound directories
When an outside directory is accessible also inside Apptainer we say it is bound, or bind mounted. The path to access it
may differ but anything you do to its content outside is visible inside and vice-versa.
By default, Apptainer binds the home of the user, /tmp
and $PWD
into the container. This means your files
at hostname:~/
are accessible inside the container. You can specify additional bind mounts using the --bind
option.
For example, let’s say /cvmfs
is available in the host, and you would like to have access to CVMFS inside the
container (here, host refers to the computer/server that you are running apptainer on). Then let’s do
apptainer shell --bind /cvmfs:/mnt centos7-devel_latest.sif
Here, the colon :
separates the path to the directory on the host (/cvmfs/
) from the mounting point (/mnt/
) inside of the
container.
If you do not have CVMFS, you can try the command with /opt
, for example.
More information on binding is provided later.
Let’s check that this works:
Apptainer> ls /mnt/cms.cern.ch
bin etc SITECONF slc7_aarch64_gcc530
bootstrap.sh external slc5_amd64_gcc434 slc7_aarch64_gcc700
...
URLs as input
Each of the different commands to set a container from a local
.sif
also accepts the URL of the image as input. For example, starting a shell with Rocky Linux 8 is as easy asapptainer shell docker://rockylinux:8
INFO: Converting OCI blobs to SIF format INFO: Starting build... Getting image source signatures Copying blob 7ecefaa6bd84 done Copying config a8f7ea56a4 done Writing manifest to image destination Storing signatures 2024/02/24 20:32:30 info unpack layer: sha256:7ecefaa6bd84a24f90dbe7872f28a94e88520a07941d553579434034d9dca399 INFO: Creating SIF file... Apptainer>
Executing commands
The command exec
starts the container from a specified image and executes a command inside it.
Let’s use the official Docker image of ROOT to start ROOT
inside a container:
apptainer exec docker://rootproject/root root -b
INFO: Converting OCI blobs to SIF format
INFO: Starting build...
...
2024/02/24 20:36:21 info unpack layer: sha256:7aea3382b6b1676fdc2742fef246a9ec593b44cf8ddc81f0f7b1638f2dda6f65
INFO: Creating SIF file...
------------------------------------------------------------------
| Welcome to ROOT 6.30/04 https://root.cern |
| (c) 1995-2024, The ROOT Team; conception: R. Brun, F. Rademakers |
| Built for linuxx8664gcc on Jan 31 2024, 10:01:37 |
| From heads/master@tags/v6-30-04 |
| With c++ (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0 |
| Try '.help'/'.?', '.demo', '.license', '.credits', '.quit'/'.q' |
------------------------------------------------------------------
root [0]
And just like that, ROOT can be used in any laptop, large-scale cluster or grid system with Apptainer available.
Execute Python with PyROOT available
Using the official Docker image of ROOT, start a Python session with PyROOT available.
Solution
apptainer exec --cleanenv docker://rootproject/root python3
--cleanenv
is optional but makes the command more robust (see Episode 4)INFO: Using cached SIF image Python 3.10.12 (main, Nov 20 2023, 15:14:05) [GCC 11.4.0] on linux Type "help", "copyright", "credits" or "license" for more information. >>> import ROOT >>> # Now you can work with PyROOT, creating a histogram for example >>> h = ROOT.TH1F("myHistogram", "myTitle", 50, -10, 10)
Key Points
Use
singularity --version
to know what you are using and to communicate it if asking for supportA container can be started from a local
.sif
or directly with the URL of the image.Apptainer is also compatible with Docker images, providing access to the large collection of images hosted by Docker Hub.
Get a shell inside of your container with
apptainer shell <path/URL to image>
Execute a command inside of your container with
apptainer exec <path/URL> <command>
Bind outside directories with
--bind
Coffee break!
Overview
Teaching: 0 min
Exercises: 15 minQuestions
Get up, stretch out, and dance!
Objectives
Refresh your mind!
Key Points
Building Containers
Overview
Teaching: 20 min
Exercises: 10 minQuestions
How to build containers with my requirements?
Objectives
Download and assemble containers from available images in the repositories.
Running containers from the available public images is not the only option. In many cases, it is required to modify
an image or even to create a new one from scratch. For such purposes, Apptainer provides the command build
,
defined in the documentation as the Swiss army knife of container creation.
The usual workflow is to prepare and test a container in a build environment (like your laptop), either with an interactive session or from a definition file, and then to deploy the container into a production environment for execution (as your institutional cluster). Interactive sessions are great to experiment and test your new container. If you want to distribute the container or use it in production, then we recommend to build it from a definition file, as described in the next episode. This ensures the greatest possibility of reproducibility and transparency.
Build a container in an interactive session
While images contained in the .sif
files are more compact and immutable objects, ideal for reproducibility, for building and testing images is
more convenient the use of a sandbox, which can be easily modified.
The command build
provides a flag --sandbox
that will create a writable directory, myAlma9
, in your work directory:
apptainer build --sandbox myAlma9 docker://almalinux:9
Notes on shared file systems like AFS
Avoid using the
AFS
(Andrew File System) and possibly other shared file systems as sandbox directory, as these systems can lead to permission issues. Symptoms could be warnings like “harmless EPERM on setxattr “security.capability” when building the sandbox and IO errors during commands execution, e.g. failure to install RPM packages. In particular, this applies to your home directory onlxplus
and to the home and nocache directories oncmslpc
. Instead, make sure to use the local file system by creating a folder in/tmp/
:mkdir /tmp/$USER
. Then, replacemyAlma9
in the previous (and next) command with/tmp/$USER/myAlma9
.
The container name is myAlma9
, and it has been initialized from the official Docker image
of AlmaLinux9.
To initialize an interactive session use the shell
command. And to write files within the sandbox directory use the --writable
option.
The --cleanenv
option is added to make sure that the host environment is not affecting the container.
Finally, the installation of new components will require superuser access inside the container, so use also the --fakeroot
option, unless you are already root also outside.
apptainer shell --writable --cleanenv --fakeroot myAlma9
Apptainer> whoami
root
--cleanenv
clears the environment. It has been added to make sure that the eventual setting of
variables on the host is not affecting the container.
Variables like PYTHONPATH or PYTHONHOME are affecting the Python execution inside the container.
A corrupted Python environment could cause errors like “ModuleNotFoundError: No module named ‘encodings’”
Apptainer environment
Environment variables in Linux are dynamic values that can affect programs and containers. You can use the environment to pass variables to a container. Apptainer by default preserves most of the outside environment inside the container but has many options to control that. You can clear the environment with the
--cleanenv
option and you can set variables with--env
. See the Apptianer manual for more options and details.PYTHONPATH and PYTHONHOME affect the Python execution, but other variables could affect other programs so, if you don’t care about the outside environment, you can add
--cleanenv
all the times you start a container (apptainer shell
,exec
andinstance start
commands).
Depending on the Apptainer/Singularity installation (privileged or unprivileged) and the version,
you may have some requirements, like the fakeroot
utility or newuidmap
and newgidmap
.
If you get an error when using --fakeroot
have a look at the fakeroot documentation.
--fakeroot
is not rootATTENTION!
--fakeroot
allows you to be root inside a container that you own but is not changing who you are outside. All the outside actions and the writing on bound files and directories will happen as your outside user, even if in the container is done by root.
As an example, let’s create a container with Pythia8 available using the myAlma9
sandbox.
First, we need to enable the CRB (Code Ready Builder/PowerTools) and
EPEL (Extra Packages for Enterprise Linux) repositories
and to install the development tools (remember that in this interactive session we are superuser):
Apptainer> dnf install 'dnf-command(config-manager)' # this installs dnf-plugins-core
Apptainer> dnf config-manager --set-enabled crb
Apptainer> dnf install epel-release
Apptainer> dnf groupinstall 'Development Tools'
Apptainer> dnf install python3-devel
Where dnf
is the package manager used in RHEL distributions
(like AlmaLinux).
Now you can follow all the installation steps described in the Pythia website. Here is a summary of the commands you will need (you may need to adjust link and commands if there is a new Pythia version):
Apptainer> mkdir /opt/pythia && cd /opt/pythia
Apptainer> curl -o pythia8310.tgz https://pythia.org/download/pythia83/pythia8310.tgz
Apptainer> tar xvfz pythia8310.tgz
Apptainer> ln -s pythia8310 pythia8
To enable the Python interface, we add the flag --with-python-include
during the configuration, pointing to the
location of the Python headers:
Apptainer> cd pythia8310
Apptainer> ./configure --with-python-include=/usr/include/python3.9
Apptainer> make
Apptainer> exit
Installing the Pythia binary package
Pythia is now distributed as RPM in EPEL, so you can use this easier alternative to install it:
Apptainer> dnf install pythia8 Apptainer> dnf install python3-pythia8 Apptainer> dnf install pythia8-devel # optional, if you need also the development libraries to compile
Now, open an interactive session with your user (no --fakeroot
). You can use now the container with Pythia8 ready in a
few steps. Let’s use the Python interface:
apptainer shell myAlma9
# These first 2 lines are necessary only if Pythia was installed from source, not if you used the binary package
Apptainer> export PYTHONPATH=/opt/pythia/pythia8/lib:$PYTHONPATH
Apptainer> export LD_LIBRARY_PATH=/opt/pythia/pythia8/lib:$LD_LIBRARY_PATH
Apptainer> python3
>>> import pythia8
>>> pythia = pythia8.Pythia()
*------------------------------------------------------------------------------------*
| |
| *------------------------------------------------------------------------------* |
| | | |
| | | |
| | PPP Y Y TTTTT H H III A Welcome to the Lund Monte Carlo! | |
| | P P Y Y T H H I A A This is PYTHIA version 8.310 | |
| | PPP Y T HHHHH I AAAAA Last date of change: 25 Jul 2023 | |
| | P Y T H H I A A | |
| | P Y T H H III A A Now is 24 Feb 2024 at 22:04:44 | |
...
| | Copyright (C) 2022 Torbjorn Sjostrand | |
| | | |
| *------------------------------------------------------------------------------* |
| |
*------------------------------------------------------------------------------------*
Notice that when installing from source we need to define the environment variables PYTHONPATH
and LD_LIBRARY_PATH
in order to use Pythia on Python.
We will automate this in the next section.
Execute Python with PyROOT available
Build a container to use Uproot, a library for reading and writing ROOT files in pure Python and NumPy, in Python 3.9.
Solution
Start from the Python 3.9 Docker image and create the
myPython
sandbox:apptainer build --sandbox myPython docker://python:3.9 apptainer shell myPython
Once inside the container, you can install Uproot.
Apptainer> python3 -m pip install --upgrade pip Apptainer> python3 -m pip install uproot awkward
Exit the container and use it as you like:
apptainer exec myPython python -c "import uproot; print(uproot.__doc__)"
Uproot: ROOT I/O in pure Python and NumPy. ...
Notice how we did not need neither
--writable
nor--fakeroot
for the installation, but everything worked fine since pip installs user packages in the user $HOME directory. In addition, Apptainer/Singularity by default mounts the user home directory as read+write, even if the container is read-only. This is why a sandbox is great to test and experiment locally, but should not be used for containers that will be shared or deployed. Manual changes and local directories are difficult to reproduce and control. Once you are happy with the content, you should use definition files, described in the next episode.
Key Points
The command
build
is the basic tool for the creation of containers.A sandbox is a writable directory where containers can be built interactively.
Superuser permissions are required to build containers if you need to install packages or manipulate the operating system.
Use interactive builds only for development and tests, use definition files for production or publicly distributed containers.
Containers from definition files
Overview
Teaching: 20 min
Exercises: 20 minQuestions
How to easily build and deploy containers from a single definition file?
Objectives
Create a container from a definition file.
As shown in the previous chapter, building containers with an interactive session may take several steps, and it can become as complicated as the required setup. An Apptainer definition file provides an easy way to build and deploy containers.
Hello World Apptainer
The following recipe shows how to build a hello-world container, and run the container on your local computer.
-
Step 1: Open a text editor (e.g., nano, vim, or gedit in a graphical environment)
nano hello-world.def
-
Step 2: Include the following script in the
hello-world.def
file to define the environmentBootStrap: docker From: ubuntu:20.04 %runscript echo "Hello World" # Print Hello world when the image is loaded
In the above script, the first line -
BootStrap: docker
indicates that apptainer will use the docker protocol to retrieve the base OS to start the image. TheFrom: ubuntu:20.04
is given to apptainer to start from a specific image/OS in Docker Hub. Any content within the%runscript
will be written to a file that is executed when one runs the apptainer image. Theecho "Hello World"
command will print theHello World
on the terminal. Finally the#
hash is used to include comments within the definition file. -
Step 3: Build the image
apptainer build hello-world.sif hello-world.def
The
hello-world.sif
file specifies the name of the output file that is built when using theapptainer build
command. -
Step 4: Run the image
./hello-world.sif
Deleting Apptainer image
To delete the hello-world Apptainer image, simply delete the hello-world.sif
file.
apptainer delete
Note that there is also a
apptainer delete
command, but it is to delete an image from a remote library. To learn more about using remote endpoints and pulling and pushing images from or to libraries, read Remote Endpoints and Library API Registries.
Example of a more elaborated definition file
Let’s look at the structure of the definition file with another example. Let’s prepare a container from an official Ubuntu image, but this time we will install ROOT with RooFit and Python integration.
Following the ROOT instructions to download a pre-compiled binary distribution, the definition file will look like
BootStrap: docker
From: ubuntu:20.04
%post
apt-get update -y
apt-get install wget -y
export DEBIAN_FRONTEND=noninteractive
apt-get install dpkg-dev cmake g++ gcc binutils libx11-dev libxpm-dev \
libxft-dev libxext-dev python libssl-dev libgsl0-dev libtiff-dev -y
cd /opt
wget https://root.cern/download/root_v6.22.06.Linux-ubuntu20-x86_64-gcc9.3.tar.gz
tar -xzvf root_v6.22.06.Linux-ubuntu20-x86_64-gcc9.3.tar.gz
%environment
export PATH=/opt/root/bin:$PATH
export LD_LIBRARY_PATH=/opt/root/lib:$LD_LIBRARY_PATH
export PYTHONPATH=/opt/root/lib
%runscript
python /opt/root/tutorials/roofit/rf101_basics.py
%labels
Author HEPTraining
Version v0.0.1
%help
Example container running the RooFit tutorial and producing the rf101_basics.png image.
The container provides ROOT with RooFit and Python integration running on Ubuntu.
Let’s take a look at the definition file:
- The first two lines define the base image. In this case, the image
ubuntu:20.04
from Docker Hub is used. %post
are lines to execute inside the container after the OS has been set. In this example, we are listing the steps that we would follow to install ROOT with a precompiled binary in an interactive session. Notice that the binary used corresponds with the Ubuntu version defined at the second line.%environment
is used to define environment variables available inside the container. Here we are setting the env variables required to execute ROOT and PyROOT.- Apptainer containers can be executable.
%runscript
define the actions to take when the container is executed. To illustrate the functionality, we will just run rf101_basics.py from the RooFit tutorial. %labels
add custom metadata to the container.%help
it is the container documentation: what it is and how to use it. Can be displayed usingapptainer run-help
Save this definition file as rootInUbuntu.def
. To build the container, just provide the definition file as argument
(executing as superuser):
apptainer build rootInUbuntu.sif rootInUbuntu.def
Then, an interactive shell inside the container can be initialized with apptainer shell
, or
a command executed with apptainer exec
. A third option is execute the actions defined inside %runscript
simply by calling the container as an executable
./rootInUbuntu.sif
RooFit v3.60 -- Developed by Wouter Verkerke and David Kirkby
Copyright (C) 2000-2013 NIKHEF, University of California & Stanford University
All rights reserved, please read http://roofit.sourceforge.net/license.txt
...
PARAMETER CORRELATION COEFFICIENTS
NO. GLOBAL 1 2
1 0.02723 1.000 0.027
2 0.02723 0.027 1.000
[#1] INFO:Minization -- RooMinimizer::optimizeConst: deactivating const optimization
RooRealVar::mean = 1.01746 +/- 0.0300144 L(-10 - 10)
RooRealVar::sigma = 2.9787 +/- 0.0219217 L(0.1 - 10)
Info in <TCanvas::Print>: png file rf101_basics.png has been created
You will find the output file rf101_basics.png
in the location where the container was executed.
If you don’t have a DISPLAY setup, Root may complain. Ignore the error messages, the image will be created anyway,
Here we have covered the basics with a few examples focused to HEP software. Check the Apptainer docs to see all the available options and more details related to the container creation.
A few best practices for your containers to make them more usable, portable, and secure:
- Always install packages, programs, data, and files into operating system locations (e.g. not
/home
,/tmp
, or any other directories that might get commonly binded on). - Document your container. If your runscript doesn’t supply help, write a
%help
or%apphelp
section. A good container tells the user how to interact with it. - If you require any special environment variables to be defined, add them to the
%environment
and%appenv
sections of the build recipe. - Files should always be owned by a system account (UID less than 500).
- Ensure that sensitive files like
/etc/passwd
,/etc/group
, and/etc/shadow
do not contain secrets. - Build production containers from a definition file instead of a sandbox that has been manually changed. This ensures the greatest possibility of reproducibility and mitigates the “black box” effect.
Deploying your containers
Keep in mind that, while building a container may be time consuming, the execution can be immediate and anywhere your image is available. Once your container is built with the requirements of your analysis, you can deploy it in a large cluster and execute it as far as Apptainer is available on the site.
Libraries like Sylabs Cloud Library ease the distribution of images. Your institution (e.g. Fermilab or CERN) may provide an Harbor registry. GitHub has a Container Registry that Apptainer can access via the ORAS API. Organizations like OSG provide instructions to use available images and distribute custom images via CVMFS.
Be smart, and this will open endless possibilities in your workflow.
Write a definition file to build a container with Pythia8 available in Python
Following the example of the first section in which a container is built with an interactive session (see the previous episode), write a definition file to deploy a container with Pythia8 available.
Take a look at
/opt/pythia/pythia8310/examples/main01.py
and define the%runscript
to execute it usingpython3
.(Tip: notice that main01.py requires
Makefile.inc
).Solution
BootStrap: docker From: almalinux:9 %post yum -y groupinstall 'Development Tools' yum -y install python3-devel mkdir /opt/pythia && cd /opt/pythia curl -o pythia8310.tgz https://pythia.org/download/pythia83/pythia8310.tgz tar xvfz pythia8310.tgz cd pythia8310 ./configure --with-python-include=/usr/include/python3.9 make %environment export PYTHONPATH=/opt/pythia/pythia8310/lib:$PYTHONPATH export LD_LIBRARY_PATH=/opt/pythia/pythia8310/lib:$LD_LIBRARY_PATH %runscript cp /opt/pythia/pythia8310/Makefile.inc . python3 /opt/pythia/pythia8310/examples/main01.py %labels Author HEPTraining Version v0.0.2 %help Container providing Pythia 8.310. Execute the container to run an example. Open it in a shell to use the Pythia installation with Python 3.9
Build your container executing
apptainer build pythiaInAlma9.sif myPythia8.def
And finally, execute the container to run
main01.py
./pythiaInAlma9.sif
This solution is building Pythia from scratch and may take several minutes to build the container. In the previous episode we saw that binary packages of Pythia are available in EPEL. Use them to build a similar container mych faster.
Key Points
An Apptainer definition file provides an easy way to build and deploy containers.
Coffee break!
Overview
Teaching: 0 min
Exercises: 15 minQuestions
Get up, stretch out, take a short break.
Objectives
Refresh your mind.
Key Points
Sharing files between host and container
Overview
Teaching: 30 min
Exercises: 0 minQuestions
How to read and write files on the host system from within the container?
Objectives
Map directories on your host system to directories within your container.
Learn about the bind paths included automatically in all containers.
One of the key features about containers is the isolation of the processes running inside them. It means, files on the host system are not accessible within the container. However, it is very common that some files on the host system are needed inside the container, or you want to write files from the container to some directory in the host.
We have already used the option --bind
earlier in the module when exploring the options available to run Apptainer
containers. In this chapter we will explore further options to bind directories from your host system to directories
within your container.
Remember that in Apptainer, your user outside is the same inside the container (except when using fakeroot). And the same happens with permissions and ownership for files in bind directories.
Bind paths included by default
For each container executed, Apptainer binds automatically some directories by default, and other defined by the system admin in the Apptainer configuration. By default, Apptainer binds:
- The user’s home directory ($HOME)
- The current directory when the container is executed ($PWD)
- System-defined paths:
/tmp
,/proc
,/dev
, etc. Since this is defined in the configuration, it may vary from site to site.
Let’s use for example the container built during the last chapter called rootInUbuntu.sif
. Take a look at your
current directory
pwd
/home/myuser/somedirectory
Open a shell inside the container and try to use pwd
again
apptainer shell rootInUbuntu.sif
Apptainer> pwd
/home/myuser/somedirectory
you will notice that the files stored on the host are located inside the container! As we explained above, Apptainer
mounts automatically your $HOME
inside the container.
Disabling system binds
If for any reason you want to execute a container removing the default binds, the command-line option
--no-mount
is available. For example, to disable the bind of/tmp
run --no-mount tmp my_container.sif
Try this time with
apptainer shell --no-mount home,cwd rootInUbuntu.sif
and you will notice that $HOME
is not mounted anymore
ls /home/myuser
ls: cannot access '/home/myuser': No such file or directory
Note how we disabled both home
and cwd
(current working directory). This because if you are running the apptainer
command from your home directory, even if you use --no-mount home
the home directory may still be mounted
because it is also your current directory.
User-defined bind paths
Apptainer provides mechanisms to specify additional binds when executing a container via command-line or environment variables. Apptainer offers a complex set of mechanism for binds or other mounts. Here we present the main points, refer to the Bind Paths and Mounts documentation for more.
Bind with command-line options
The command-line option --bind (-B)
will specify the directories that must be linked between the
host and the container. It is available for run
, exec
, and shell
(as well for instance
that is
not covered yet).
The syntax for using the bind option is "source:destination"
, and the paths must be absolute (relative
paths will be rejected). For example, let’s create a directory in the host containing a constant that can be useful
for your analysis
mkdir $HOME/mydata
echo "MUONMASS=105.66 MeV" > $HOME/mydata/muonMass.txt
It is very, very important in your analysis workflow to know the mass of the muon, right? It may have sense to put the data
in a high-level directory within the container, like /data
apptainer shell --bind $HOME/mydata:/data rootInUbuntu.sif
This will bind the directory mydata/
from the host as /data
inside the container:
ls -l /data
-rw-rw-r-- 1 myuser myuser 20 Jan 2 12:46 muonMass.txt
Now you can use the mass of the muon from a root-level directory!
If multiple directories must be available in the container, you can repeat the option or they can be defined with a comma between each pair of directories,
i.e. using the syntax source1:destination1,source2:destination2
.
Also. If the destination is not specified, it will be set as equal as the source. For example
apptainer shell --bind /cvmfs rootInUbuntu.sif
Will mount /cvmfs
inside the container. Try it!
Binding directories with Docker-like syntax using
--mount
The flag –mount provides a method to bind directories using the syntax of Docker. The bind is specified with the format
type=bind,src=<source>,dst=<dest>
. Currently, onlytype=bind
is supported. Check the documentation for additional options available.
Bind with environment variables
If the environment variable $APPTAINER_BIND
is defined, apptainer will bind inside ANY container
the directories specified in the format source
, with the destination being optional (in the same way as using
--bind
). For example:
export SINGULARITY_BIND="/cvmfs"
will bind CVMFS to all your Apptainer containers (/cvmfs
must be available in the host, of course).
You can also bind multiple directories using commas between each source:destination
.
Key Points
Bind mounts allow reading and writing files within the container.
In Apptainer, you have same owner and permissions for files inside and outside the container.
Some paths are mounted by default by Apptainer.
Additional directories to bind can be defined using the
--bind
option or the environment variable$SINGULARITY_BIND
.
Apptainer/Singularity instances
Overview
Teaching: 40 min
Exercises: 10 minQuestions
How can I keep my container running in the background?
What are the use cases for instances?
Objectives
Run containers in a detached mode to keep services up.
Deploy instances via definition files.
As we have studied in previous chapters, commands such as run
and shell
allocate Apptainer/Singularity
containers in the foreground, stopping any process running inside the container after logout. This behavior
suits the use case of containers for executing interactive commands in a well-defined environment, but there
are cases when running processes in the background is convenient. For example, when a web application like a
Jupyter notebook is deployed inside a container, it is desired to keep the container up while it waits for connections
from the web browser.
Apptainer provides the concept of instances to deploy services in the background. While Docker is a common choice of tool for setting services, Apptainer has the advantage of working without requiring any special permissions (like when you are working in a cluster provided by your university/laboratory). In this chapter we will learn the basics about their capabilities and some use cases as examples.
Instances from image files
To start an instance, Apptainer provides the command instance
. To exemplify, let’s pull the AlmaLinux image used
in previous chapters
apptainer pull docker://almalinux:9
The image must be started in the following way:
apptainer instance start almalinux_9.sif myalma9
In this example, the .sif
is the image downloaded from Dockerhub, and mycentos7
is the name that we have
assigned to the instance. Instead of opening a shell session or executing a command, the container is running in
the background.
Confirm that the instance is running using the instance list
command
apptainer instance list
INSTANCE NAME PID IP IMAGE
myalma9 3277300 /tmp/myuser/almalinux_9.sif
To interact with the instance, the commands exec
and shell
are available. The instance must be referred as
instance://name
.
For example, to open a shell inside the CentOS instance:
apptainer shell instance://myalma9
Remember that exiting the shell instance will not stop the container. For doing so, use instance stop
:
apptainer instance stop myalma9
You can confirm the instance doesn’t exist with instance list
.
Instances with bind paths
When starting an instance, the same options for bind directories between the host and the container as running an interactive session are available. For example, if you want a directory mounted inside the instance, use the
--bind
option:apptainer instance start --bind /home/user/mydata:/data almalinux_9.sif myalma9
binding the directory
mydata/
from the host as/data
inside the instance.
A web server as an instance
One of the main purposes of the Apptainer instances is deploying services with customized environments. Before moving to more complex use cases, let’s start with a basic example: a web service showing a HTML with a message.
Let’s write a basic index.html
file as:
<!DOCTYPE html>
<html>
<head>
<title>My awesome service</title>
</head>
<body>
<h1>Hello world!</h1>
<p>If you see this page, my awesome service is up and running.</p>
</body>
</html>
If you are not familiar with HTML take a quick look at the HTML Tutorial, but it is not mandatory. What really matters is having a minimal webpage that our server will show.
Now, let’s prepare a basic web server using Python http.server.
Create a definition file, saved as basicServer.def
, which contains:
Bootstrap: docker
From: ubuntu:20.04
%post
apt-get update -y
apt-get install -y python3.9
%files
index.html /tmp/index.html
%startscript
cd /tmp
python3.9 -m http.server 8850
If you recall the chapter about definition files,
this definition file will pull the official Ubuntu image from Dockerhub, and will install Python3.9.
In addition, it copies index.html
in /tmp
inside the container. When the instance starts, commands specified on
%startscript
are executed. On this example, http.server
will be executed, serving a page in the port 8850 (you can
use any other port if 8850 is busy with another service).
Let’s build an image from the definition. Remember that building images requires either superuser permissions or
using the flag --fakeroot
as
apptainer build --fakeroot basicServer.sif basicServer.def
Now, let’s start an instance named myWebService
with the image that we just built
apptainer instance start --no-mount tmp --cleanenv basicServer.sif myWebService
Reminder from the previous chapter: with --no-mount tmp
we are asking Apptainer to NOT bind /tmp
from the host
to the instance (it is mounted by default), we use instead an isolated /tmp
inside the instance where index.html has
been copied.
And with --cleanenv
we clear the environment. It is not always necessary but it prevents interferences
from the host environment (see ).
You can confirm in the terminal that the web service is up using curl
as
curl http://localhost:8850
<!DOCTYPE html>
<html>
<head>
<title>Welcome to my service!</title>
</head>
<body>
<h1>Hello world!</h1>
<p>If you see this page, my awesome service is up and running.</p>
</body>
</html>
If you are executing Apptainer locally, try to open http://localhost:8850.
SSH tunneling
If you are deploying a service in a cluster of your institution (as LXPLUS at CERN) it is likely that you need SSH tunneling for opening pages served by your service with a web browser. A basic port forwarding can be configured as:
ssh -L <port>:localhost:<port> myuser@<server>
where
is the one used by your service, and is the address of your institutional resources. For example, for connecting to LXPLUS forwarding the port 8850: ssh -L 8850:localhost:8850 myuser@lxplus.cern.ch
Then you can open http://localhost:8850 in your machine!
Port numbers less than 1024 are privileged ports and can be used only by root. Please consult the allowed ports and rules with your institution.
Remember to stop the instance once you are done.
Serving a Jupyter notebook with custom environment
As an example of the capabilities of instances as services, let’s extend our definition file to deploy a Jupyter notebook server with a customized environment. Jupyter Notebook is a web-based interactive computing platform. The notebook combines live code, equations, narrative text, and visualizations.
What if we provide a Jupyter notebook ready to use ROOT? If you remember our example from the definition files chapter, at this point it must be almost straightforward:
Bootstrap: docker
From: ubuntu:20.04
%post
apt-get update -y
apt-get install -y python3
apt-get install -y python3-pip
pip install notebook
apt-get install wget -y
export DEBIAN_FRONTEND=noninteractive
apt-get install dpkg-dev cmake g++ gcc binutils libx11-dev libxpm-dev \
libxft-dev libxext-dev python libssl-dev libgsl0-dev libtiff-dev -y
cd /opt
wget https://root.cern/download/root_v6.22.06.Linux-ubuntu20-x86_64-gcc9.3.tar.gz
tar -xzvf root_v6.22.06.Linux-ubuntu20-x86_64-gcc9.3.tar.gz
%environment
export PATH=/opt/root/bin:$PATH
export LD_LIBRARY_PATH=/opt/root/lib:$LD_LIBRARY_PATH
export PYTHONPATH=/opt/root/lib
%startscript
jupyter notebook --port 8850
Save the definition file as jupyterWithROOT.def
, and let’s build an image called jupyterWithROOT.sif
apptainer build --fakeroot jupyterWithROOT.sif jupyterWithROOT.def
Now, start an instance named mynotebook
with our brand-new image.
Consider using --cleanenv
if needed.
apptainer instance start jupyterWithROOT.sif mynotebook
and confirm that the instance is up
apptainer instance list
INSTANCE NAME PID IP IMAGE
mynotebook 10720 /home/myuser/jupyterWithROOT.sif
If you go to http://localhost:8850 (with SSH tunneling if needed), you will find out that for security reasons the
Jupyter webapp will ask for an access token. Fortunately, you can get the token listing the URL of active servers using
the jupyter notebook list
command. To execute the command inside the instance, use sigularity exec
:
apptainer exec instance://mynotebook jupyter notebook list
Currently running servers:
http://localhost:8850/?token=12asldc9b2084f9b664b39a6246022312bc9c605b :: /home/myHome
Notebook starting on a different port!
If the chosen port for the Notebook (8850 stated in the SIF file) is not available, the notebook will not error out, but will start and use the first available port after that. E.g. if you did not terminate the web server from the previous example, The above command “jupyter notebook list” will show you the correct port.
Open the URL with the token (from http to the first space), and you will be able to see the Jupyter interface. Try to open a new notebook and write in a cell to confirm that ROOT is available:
import ROOT
# Now you can work with PyROOT, creating a histogram for example
h = ROOT.TH1F("myHistogram", "myTitle", 50, -10, 10)
h.FillRandom("gaus", 10000)
c = ROOT.TCanvas("myCanvasName","The Canvas Title",800,600)
h.Draw()
c.Draw()
The bottom line: with any Jupyter notebook that you write, you can provide an Apptainer image that will set the environment required to execute the cells. It doesn’t matter if yourself or someone else comes in one, five, ten years, your code will work independently of the software available in your computer as long as Apptainer/Singularity is available!
A Jupyter notebook with Uproot available
Can you setup a Jupyter notebook server with Uproot available in Apptainer?
Hint: Uproot can be installed using
pip
.Solution
Bootstrap: docker From: ubuntu:20.04 %post apt-get update -y apt-get install -y python3 apt-get install -y python3-pip pip install notebook pip install uproot %startscript jupyter notebook --port 8850
Confirm that Uproot is available opening a notebook and executing in a cell
import uproot print(uproot.__doc__)
Key Points
Instances allow to setup services via Apptainer images or definition files.
Code provided in Jupyter notebooks can be accompanied by a Apptainer/Singularity image with the environment needed for its execution, ensuring the reproducibility of the results.
Bonus Episode: Building and deploying an Apptainer container to GitHub Packages
Overview
Teaching: 40 min
Exercises: 0 minQuestions
How to build apptainer container for python packages?
How to share apptainer images?
Objectives
To be able to build an apptainer container and share it via GitHub packages
Prerequisites
The previous episode ended the Introduction to Apptainer/Singularity. This bonus episode is an optional extension mixing knowledge from different courses. For this lesson, you will also need,
- Knowledge of Git SW Carpentry Git-Novice Lesson (for simplified authentication with the
gh
CLI, see this version of the git training)- Knowledge of GitHub CI/CD HSF Github CI/CD Lesson
Apptainer Container for python packages
Python packages can be installed using an Apptainer image. The following example illustrates how to write a definition file for building an image containing python packages.
BootStrap: docker
From: ubuntu:20.04
%post
apt-get update -y
apt-get install wget -y
export DEBIAN_FRONTEND=noninteractive
apt-get install dpkg-dev cmake g++ gcc binutils libx11-dev libxpm-dev \
libxft-dev libxext-dev python3 libssl-dev libgsl0-dev libtiff-dev \
python3-pip -y
pip3 install numpy
pip3 install awkward
pip3 install uproot4
pip3 install particle
pip3 install hepunits
pip3 install matplotlib
pip3 install hist
pip3 install mplhep
pip3 install vector
pip3 install fastjet
pip3 install iminuit
As we see, several packages are installed.
Publish Apptainer images with GitHub Packages and share them!
It is possible to publish apptainer images with GitHub packages. To do so, one needs to use GitHub CI/CD. A step-by-step guide is presented here.
- Step 1: Create a GitHub repository and clone it locally.
- Step 2: In the empty repository, make a folder called
.github/workflows
. In this folder we will store the file containing the YAML script for a GitHub workflow, namedapptainer-build-deploy.yml
(the name doesn’t really matter). - Step 3: In the top directory of your GitHub repository, create a file named
Apptainer
. - Step 4: Copy-paste the content above and add to the Apptainer file. (In principle it is possible to build this image locally, but we will not do that here, as we wish to build it with GitHub CI/CD).
- Step 5: In the
apptainer-build-deploy.yml
file, add the following content:
name: Apptainer Build Deploy
on:
pull_request:
push:
branches: master
jobs:
build-test-container:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
container:
image: quay.io/singularity/singularity:v4.1.0
options: --privileged
name: Build Container
steps:
- name: Check out code for the container builds
uses: actions/checkout@v4
- name: Build Container
run: |
singularity build container.sif Apptainer
- name: Login and Deploy Container
run: |
echo ${{ secrets.GITHUB_TOKEN }} | singularity remote login -u ${{ secrets.GHCR_USERNAME }} --password-stdin oras://ghcr.io
singularity push container.sif oras://ghcr.io/${GITHUB_REPOSITORY}:${tag}
The above script is designed to build and publish an Apptainer image with GitHub packages.
- Step 6: Add LICENSE and README as recommended in the SW Carpentry Git-Novice Lesson, and then the repository is good to go.
Key Points
Python packages can be installed in apptainer images along with ubuntu packages.
It is possible to publish and share apptainer images over github packages.