Conda environments with Apptainer
If an application provides Conda installation instructions or an
environment.yml file, install that environment inside a container.
Build the container once, save its SIF image on /project, and reuse it in
Slurm jobs. Neither a host Conda installation nor conda activate is needed
to run it. This example uses the existing apptainer/1.4.4 module and
micromamba inside the image.
For an overview of the available installation methods, see Installing Python packages.
The environment is installed at /opt/env inside the container. This path
remains the same in every job, preserving absolute paths in installed packages.
The SIF file itself can be moved or shared. Independent jobs can use the same
read-only image, with separate writable temporary directories. Apptainer manages
the mounts; no user-maintained mount helper or shared writable overlay is needed.
Prepare the environment and container definition
Download these files into the same directory on your project storage:
The sample environment contains Python and NumPy:
channels:
- conda-forge
- nodefaults
dependencies:
- python=3.11
- numpy=2.4
- pip
For your application, replace the sample dependencies and channels with the
upstream requirements. Remove a machine-specific prefix: from an exported
YAML file; the definition explicitly chooses /opt/env. Packages available
only through pip can be included in a pip: dependency subsection. If the
upstream installation also requires source files, build tools or external data,
include those steps explicitly in the definition; the YAML alone may not be
the complete installation procedure.
The definition copies the YAML into the image, installs the environment, records the resolved packages, and removes the package cache:
Bootstrap: docker
From: mambaorg/micromamba:2.9.0
%files
environment.yml /opt/environment.yml
%post
set -eu
export MAMBA_ROOT_PREFIX=/opt/conda
micromamba create --yes --prefix /opt/env --file /opt/environment.yml
micromamba list --prefix /opt/env --explicit > /opt/conda-explicit.txt
/opt/env/bin/python -m pip freeze > /opt/pip-freeze.txt
micromamba clean --all --yes
%environment
export PATH="/opt/env/bin:$PATH"
export PYTHONNOUSERSITE=1
export PYTHONDONTWRITEBYTECODE=1
%runscript
exec micromamba run --prefix /opt/env "$@"
%test
/opt/env/bin/python -c 'import numpy as np; assert np.arange(5).sum() == 10; print("NumPy test passed")'
The %runscript activates the environment through micromamba run, including
package activation hooks, and runs the supplied command. %test checks the
sample NumPy installation; replace it with a smoke test for your own application.
Keep the definition and environment file with the image. For reproducible builds,
pin the base image by digest and use exact package versions or an upstream
lockfile supported by micromamba; the sample version constraints alone are not
a complete lockfile. The image also contains /opt/conda-explicit.txt and
/opt/pip-freeze.txt as build records.
Build on a compute node
Request an interactive allocation, then change to the directory containing the downloaded files:
srun -p test -N 1 -n 1 -c 8 --mem-per-cpu=4000 --time=01:00:00 --pty bash -l
cd "/project/${project}/container-recipe"
module load apptainer/1.4.4
The test partition has a one-hour limit; use an appropriate partition for longer builds.
Inside this allocation, run:
(
set -euo pipefail
IMAGE_DIR="/project/${project}/images"
mkdir -p "$IMAGE_DIR"
BUILD_WORK=$(mktemp -d /tmp/conda-container-build.XXXXXXXX)
trap 'rm -rf -- "$BUILD_WORK"' EXIT
export APPTAINER_TMPDIR="$BUILD_WORK/tmp"
export APPTAINER_CACHEDIR="$BUILD_WORK/cache"
mkdir -p "$APPTAINER_TMPDIR" "$APPTAINER_CACHEDIR"
apptainer build --fakeroot "$IMAGE_DIR/environment-v1.sif" environment.def
sha256sum "$IMAGE_DIR/environment-v1.sif" > "$IMAGE_DIR/environment-v1.sif.sha256"
)
No sudo is required. The %files source path is relative to the build’s
working directory, so keep environment.yml beside environment.def and
run the command there. The first build needs network access to the base-image
registry and package channels.
Both the temporary build tree and the Apptainer cache must stay on local storage.
Otherwise Apptainer’s default cache under $HOME/.apptainer would put build
files on Lustre. On the tested node, /tmp is RAM-backed: allow memory for the
unpacked container, downloaded packages and build processes, not just the final
compressed image. df reports node-wide space, not your job’s memory limit.
The subshell removes its temporary build directory on exit.
Run from Slurm
The downloadable job script uses one node, activates the container environment, and forwards the application command and arguments:
#!/bin/bash
#SBATCH --job-name=conda-container
#SBATCH --partition=test
#SBATCH --nodes=1
#SBATCH --ntasks=1
#SBATCH --cpus-per-task=8
#SBATCH --mem-per-cpu=4000
#SBATCH --time=00:10:00
# Usage: sbatch run-environment.sbatch \
# "/project/${project}" "/project/${project}/images/environment-v1.sif" \
# python simulation.py
set -euo pipefail
if (( $# < 3 )); then
echo "Usage: PROJECT_DIRECTORY IMAGE COMMAND [ARG...]" >&2
exit 1
fi
PROJECT_DIR=$(realpath -e -- "$1")
IMAGE=$(realpath -e -- "$2")
shift 2
module load apptainer/1.4.4
JOB_WORK=$(mktemp -d /tmp/conda-container.XXXXXXXX)
trap 'rm -rf -- "$JOB_WORK"' EXIT
mkdir -p "$JOB_WORK/cache" "$JOB_WORK/mamba"
# The image is shared read-only; each job has its own writable scratch space.
srun apptainer run --cleanenv --contain \
--bind "$PROJECT_DIR:/work" --bind "$JOB_WORK:/jobtmp" --pwd /work \
--env TMPDIR=/jobtmp,XDG_CACHE_HOME=/jobtmp/cache,MAMBA_ROOT_PREFIX=/jobtmp/mamba \
--env NUMBA_CACHE_DIR=/jobtmp/cache/numba,MPLCONFIGDIR=/jobtmp/cache/matplotlib \
--env OMP_NUM_THREADS="$SLURM_CPUS_PER_TASK",OPENBLAS_NUM_THREADS="$SLURM_CPUS_PER_TASK" \
--env NUMBA_NUM_THREADS="$SLURM_CPUS_PER_TASK" \
"$IMAGE" "$@"
For a first check, submit it from the login node:
sbatch run-environment.sbatch \
"/project/${project}" "/project/${project}/images/environment-v1.sif" \
python -c 'import numpy as np; print(np.arange(5).sum())'
The Slurm output should contain 10. To run your own program, place
simulation.py in your project directory and replace the last line with
python simulation.py. The project directory is visible as /work inside
the container, which is also its working directory; write persistent results
there. Add explicit bind mounts if your application needs other project or
scratch directories. Adapt the partition, time, CPU and memory settings
to your workload; the supplied settings are for a short test.
apptainer run invokes this image’s %runscript. apptainer exec runs a
command directly and bypasses that activation step. For an interactive check
inside an existing allocation, use the same apptainer run options as the job
script, replacing the application command with bash.
The job script isolates the home and temporary directories with --contain,
clears inherited environment settings with --cleanenv, and keeps application
caches on job-local storage. User Python site-packages are disabled in the
image. Temporary files are discarded at job exit; cleanup after a forced kill
or node failure may require the site’s normal temporary-directory cleanup.