.. _containers:
==========
Containers
==========
.. contents:: In this section:
:local:
:depth: 2
Containers are a way to package software with all of its dependencies, creating isolated and reproducible environments. At GridUnesp, we use `Apptainer `_ (formerly Singularity) to run containers.
Why Use Containers?
===================
1. **Reproducibility:** The exact same environment can be used by different users
2. **Isolation:** Conflicting dependencies do not interfere with each other
3. **Portability:** The same container works on different systems
4. **Convenience:** Distribute your environment with a single file
5. **Security:** Apptainer containers do not require root privileges
Apptainer vs Docker
===================
Apptainer is similar to Docker, but designed for HPC:
=============== ================== ================================
Feature Docker Apptainer
--------------- ------------------ --------------------------------
Permissions Requires root Normal user
HPC Integration Limited Native (MPI, GPUs)
Images Layers Single file (.sif)
Security Less secure on HPC Designed for shared environments
=============== ================== ================================
Basic Concepts
==============
- **Image:** File containing the container's filesystem (``.sif``)
- **Definition:** Text file with instructions to build the image (``.def``)
- **Sandbox:** Directory for interactive development (``apptainer shell image.sif``)
- **Registry:** Image repository (Docker Hub, etc.)
Obtaining Images
================
Method 1: Pull from Docker Hub
------------------------------
Job to download and build the container (a file with the ".sif" suffix):
.. code-block:: bash
:caption: pull_container.sh
#!/bin/bash
#SBATCH -J pull_container
#SBATCH -t 01:00:00
#SBATCH -n 1
export INPUT=""
export OUTPUT="ubuntu.sif"
job-nanny apptainer pull ubuntu.sif docker://ubuntu:20.04
In this case, the ``ubuntu.sif`` container is built, which is an image of **Ubuntu 20.04**.
Method 2: Build from a definition file
--------------------------------------
Definition file (``ubuntu.def``):
.. code-block:: text
:caption: ubuntu.def
Bootstrap: docker
From: ubuntu:20.04
%post
apt-get update
apt-get install -y python3 python3-pip
pip3 install numpy pandas matplotlib
%environment
export LC_ALL=C
%runscript
echo "Container Ubuntu com Python e pacotes científicos"
python3 "$@"
Build script (``ubuntu.sif``):
.. code-block:: bash
:caption: build_container.sh
#!/bin/bash
#SBATCH -J build_container
#SBATCH -t 02:00:00
#SBATCH -n 1
#SBATCH --mem=4G
export INPUT="ubuntu.def"
export OUTPUT="ubuntu.sif"
job-nanny apptainer build ubuntu.sif ubuntu.def
Running Containers
==================
Execution modes:
1. **apptainer run:** Runs the default command defined in the container
2. **apptainer exec:** Runs a specific command
3. **apptainer shell:** Opens an interactive shell
**Basic example:**
.. code-block:: bash
:caption: run_container.sh
#!/bin/bash
#SBATCH -J run_container
#SBATCH -t 01:00:00
#SBATCH -n 1
export INPUT="ubuntu.sif script.py dados/"
export OUTPUT="resultados/"
job-nanny apptainer exec ubuntu.sif python3 script.py
**Example with an interactive shell (for testing):**
.. code-block:: bash
apptainer shell ubuntu.sif
# Inside the container
ls -la
python3
exit
Sharing Files
=============
By default, Apptainer automatically mounts:
- The user's ``$HOME``
- The current working directory
- The system's temporary directories
**Explicit mounting:**
.. code-block:: bash
# Mount additional directories
apptainer exec --bind /path/on/host:/path/in/container ubuntu.sif command
**With job-nanny:**
.. code-block:: bash
:caption: container_com_dados.sh
#!/bin/bash
#SBATCH -J container_dados
#SBATCH -t 02:00:00
#SBATCH -n 1
export INPUT="ubuntu.sif dados/ script.py"
export OUTPUT="resultados/"
# job-nanny copies INPUT to the work area
# Inside the container, the files will be in the current directory
job-nanny apptainer exec ubuntu.sif python3 script.py
Containers with GPU
===================
To use GPUs, add the ``--nv`` flag:
.. code-block:: bash
:caption: container_gpu.def
Bootstrap: docker
From: nvidia/cuda:11.8.0-runtime-ubuntu20.04
%post
apt-get update
apt-get install -y python3 python3-pip
pip3 install torch torchvision
%runscript
python3 "$@"
Build:
.. code-block:: bash
:caption: job_gpu_container.sh
# Submit job
#!/bin/bash
#SBATCH -J gpu_container
#SBATCH --partition=gpu
#SBATCH --gres=gpu:1
#SBATCH -t 02:00:00
#SBATCH -n 1
export INPUT="container_gpu.def treinamento.py"
export OUTPUT="modelo.pth"
module load cuda/11.8 # Optional, Apptainer uses the container's CUDA
job-nanny apptainer build --nv cuda_container.sif container_gpu.def
job-nanny apptainer exec --nv cuda_container.sif python3 treinamento.py
Execution:
.. code-block:: bash
sbatch job_gpu_container.sh
Containers with MPI
===================
Apptainer can run MPI applications across multiple nodes.
**Definition file with MPI:**
.. code-block:: text
:caption: mpi_container.def
Bootstrap: docker
From: ubuntu:22.04
%environment
export OMPI_DIR=/opt/ompi
export PATH=$OMPI_DIR/bin:$PATH
export LD_LIBRARY_PATH=$OMPI_DIR/lib:$LD_LIBRARY_PATH
%post
apt-get update
apt-get install -y wget build-essential
# Install OpenMPI
export OMPI_DIR=/opt/ompi
export OMPI_VERSION=4.1.5
mkdir -p /tmp/ompi
cd /tmp/ompi
wget https://download.open-mpi.org/release/open-mpi/v4.1/openmpi-$OMPI_VERSION.tar.bz2
tar -xjf openmpi-$OMPI_VERSION.tar.bz2
cd openmpi-$OMPI_VERSION
./configure --prefix=$OMPI_DIR
make -j4 install
# Install compilers and libraries
apt-get install -y gcc g++ gfortran
%files
mpitest.c /opt/mpitest.c
**Example MPI code (mpitest.c):**
.. code-block:: c
#include
#include
#include
int main(int argc, char** argv) {
MPI_Init(&argc, &argv);
int world_rank, world_size;
char hostname[256];
MPI_Comm_rank(MPI_COMM_WORLD, &world_rank);
MPI_Comm_size(MPI_COMM_WORLD, &world_size);
gethostname(hostname, sizeof(hostname));
printf("Processo %d de %d em %s\n", world_rank, world_size, hostname);
MPI_Finalize();
return 0;
}
**Scripts for building, compiling and running:**
.. code-block:: bash
:caption: build_mpi_container.sh
#!/bin/bash
#SBATCH -J build_mpi
#SBATCH -t 02:00:00
#SBATCH -n 1
export INPUT="mpi_container.def"
export OUTPUT="mpi_container.sif"
job-nanny apptainer build mpi_container.sif mpi_container.def
.. code-block:: bash
:caption: compile_in_container.sh
#!/bin/bash
#SBATCH -J compile_mpi
#SBATCH -t 00:30:00
#SBATCH -n 1
export INPUT="mpi_container.sif mpitest.c"
export OUTPUT="mpitest"
job-nanny apptainer exec mpi_container.sif mpicc -o mpitest /opt/mpitest.c
.. code-block:: bash
:caption: run_mpi_container.sh
#!/bin/bash
#SBATCH -J run_mpi
#SBATCH -N 4
#SBATCH --ntasks-per-node=28
#SBATCH -t 01:00:00
#SBATCH --mem-per-cpu=2G
export INPUT="mpi_container.sif mpitest"
export OUTPUT="mpi_output/"
module load openmpi/4.1.5 # System MPI for orchestration
# --sharens avoids namespace conflicts
job-nanny mpirun -n 112 apptainer exec --sharens mpi_container.sif ./mpitest
This job will run 112 processes, 28 of them on each node. The *OpenMPI* used is the one already installed in the container (mpi_container.sif), and the executable (mpitest) is processed inside that container. The MPI installed in the container runs in conjunction with the system one, loaded via module (``module load openmpi/4.1.5``). Note that the *OpenMPI* version installed in the container, for the example above, is **4.1.5**, which is the same version as the module. The versions of both *OpenMPI* installations must be compatible.
.. attention::
According to the `Using –sharens mode `_ section of the Apptainer page, in order to ensure there are no *namespace*-related conflicts during parallelization, it is recommended to include the ``--sharens`` parameter right after the ``exec`` command. Like this: ``apptainer exec --sharens``.
Containers for Specific Applications
====================================
Example: GROMACS in a container
-------------------------------
.. code-block:: text
:caption: gromacs.def
Bootstrap: docker
From: ubuntu:22.04
%post
apt-get update
apt-get install -y wget cmake build-essential
# Install GROMACS
wget ftp://ftp.gromacs.org/pub/gromacs/gromacs-2023.tar.gz
tar -xzf gromacs-2023.tar.gz
cd gromacs-2023
mkdir build
cd build
cmake .. -DGMX_BUILD_OWN_FFTW=ON -DCMAKE_INSTALL_PREFIX=/usr/local/gromacs
make -j4
make install
%environment
export PATH=/usr/local/gromacs/bin:$PATH
export LD_LIBRARY_PATH=/usr/local/gromacs/lib:$LD_LIBRARY_PATH
.. code-block:: bash
:caption: build_gromacs.sh
#!/bin/bash
#SBATCH -J build_gromacs
#SBATCH -t 04:00:00
#SBATCH -n 1
export INPUT="gromacs.def"
export OUTPUT="gromacs.sif"
job-nanny apptainer build gromacs.sif gromacs.def
.. code-block:: bash
:caption: run_gromacs.sh
#!/bin/bash
#SBATCH -J gromacs_container
#SBATCH -N 2
#SBATCH --ntasks-per-node=28
#SBATCH -t 24:00:00
export INPUT="gromacs.sif topol.tpr"
export OUTPUT="resultado_gromacs/"
module load openmpi/4.1.5
job-nanny mpirun -n 56 apptainer exec gromacs.sif mdrun_mpi -deffnm resultado
Example: Python with specific libraries
---------------------------------------
.. code-block:: text
:caption: python_sci.def
Bootstrap: docker
From: python:3.9-slim
%post
pip install --upgrade pip
pip install numpy scipy pandas matplotlib scikit-learn jupyter
%runscript
python "$@"
.. code-block:: bash
:caption: build_python_sci.sh
#!/bin/bash
#SBATCH -J build_python_sci
#SBATCH -t 02:00:00
#SBATCH -n 1
export INPUT="python_sci.def"
export OUTPUT="python_sci.sif"
job-nanny apptainer build python_sci.sif python_sci.def
.. code-block:: bash
:caption: run_python.sh
#!/bin/bash
#SBATCH -J python_container
#SBATCH -n 1
#SBATCH -t 02:00:00
export INPUT="python_sci.sif analise.py dados.csv"
export OUTPUT="resultados_analise/"
job-nanny apptainer exec python_sci.sif python analise.py
Best Practices with Containers
==============================
1. **Lightweight images**
- Use minimal base images (`slim `_, `alpine `_)
- Remove unnecessary caches in %post
.. code-block:: text
%post
apt-get update && apt-get install -y \
git \
python3 \
&& rm -rf /var/lib/apt/lists/*
2. **Versioning**
.. code-block:: text
Bootstrap: docker
From: ubuntu:20.04 # Fixed version, not "latest"
3. **Reproducibility**
- Include exact package versions
- Document the build process
4. **Security**
- Do not put passwords or keys in the image
- Use environment variables for sensitive settings
5. **Organization**
.. code-block:: text
meus_containers/
├── ubuntu-python/
│ ├── ubuntu-python.def
│ ├── build.slurm
│ └── ubuntu-python.sif
├── gromacs/
│ ├── gromacs.def
│ └── build.slurm
└── mpi-base/
├── mpi.def
└── README.md
6. **Cleanup**
- Remove unnecessary caches in ``/home/$USER/.apptainer/``
.. code-block:: text
apptainer cache clean
Troubleshooting
===============
**Error: "no space left on device" during build**
- Use the ``job-nanny`` option ``LARGE_FILES="true"``.
.. code-block:: bash
#!/bin/bash
#SBATCH -J build_ubuntu
#SBATCH -t 02:00:00
#SBATCH -n 1
export INPUT="ubuntu.def"
export OUTPUT="ubuntu.sif"
export LARGE_FILES="true"
job-nanny apptainer build ubuntu.sif ubuntu.def
**Error: "permission denied"**
- Check the .sif file permissions
- Make sure the container does not require root
**Error: "GPU not available" inside the container**
- Use the ``--nv`` flag
- Check whether CUDA is installed in the container
**Error: "MPI version mismatch"**
- Use the ``--sharens`` flag
- Compile MPI in the container with the same version as the system (or a lower version)
.. seealso::
- :ref:`using_job_nanny`
- :ref:`installing_applications` - Installation alternatives
- :ref:`gpu_usage`
- :ref:`distributed_memory` - MPI
- `Apptainer Documentation `_