Mastering Container Usage on DGX On-Prem
Table of Contents
1. Introduction
Before diving into the technical details, let's understand why this setup is valuable:
Reproducibility: Your code runs the same way every time, regardless of the compute node
Environment Management: No more dependency conflicts or "it works on my machine" issues
Resource Efficiency: Share optimized, pre-built environments across your team
Flexibility: Switch between different frameworks (PyTorch, TensorFlow, etc.) without system-level changes
2. Understanding the Technology Stack
Running containerized workloads on the DGX On-Prem cluster combines three powerful technologies that work seamlessly together to give you the flexibility of containers with the performance of HPC:
SLURM: Your job scheduler and resource manager
Enroot: A container runtime built for HPC environments
Pyxis: The bridge that connects SLURM and Enroot automatically
This guide will help you understand these tools and use them effectively, whether you're running quick experiments or large-scale training jobs.
SLURM
Think of SLURM (Simple Linux Utility Resource Management) as the traffic controller for your cluster. It:
Decides which jobs run when and where
Allocates GPUs, CPUs, and memory fairly among users
Handles queues when the cluster is busy
Monitors job progress and resource usage
Enroot
Enroot is like Docker's HPC-savvy cousin. Unlike Docker, it:
Runs without requiring administrator privileges (no root access needed)
Focuses on performance rather than security isolation
Integrates naturally with shared filesystems
Handles GPU access efficiently
Pyxis
Pyxis is the magic that makes everything work together automatically. When you submit a job, it:
Downloads your container image if needed
Sets up the container environment
Ensures your job has access to GPUs and data
Cleans up when your job finishes
How It All Works Together
Let's compare three approaches to running the same PyTorch container.
Traditional Docker (Local Machine)
This is how you would normally do it when running containers from your own computer:
# Download container image
docker pull nvcr.io/nvidia/pytorch:24.11-py3
# Create container instance
docker create --name pytorch_container nvidia/pytorch:24.11-py3
# Run container
docker run pytorch_containerEnroot Equivalent (HPC Environments)
These are the Enroot equivalent commands (when compared to Docker) that Pyxis automatically runs behind the scenes:
# Import image
enroot import docker://nvcr.io/nvidia/pytorch:24.11-py3
# Create container instance
enroot create --name pytorch_container nvidia+pytorch+24.11-py3.sqsh
# Start container and run task
enroot start pytorch_container python train_model.pySLURM + Pyxis (DGX On-Prem)
The SLURM + Pyxis approach is clearly the simplest - you just specify which container you want, and everything else happens automatically!
#!/bin/bash
#SBATCH --job-name=pytorch_job
#SBATCH --container-image='docker://nvcr.io/nvidia/pytorch:24.11-py3'
# Your job commands here
python train_model.pyThe following diagram illustrates how these different parts interact to each other from a very high-level perspective.
3. Container Deployment Strategies
Ephemeral Containers
This is a quick and clean approach for on-demand tasks. It's best for experiments, one-off jobs, or when you want the latest version every time. The container is downloaded fresh, your job runs, then everything is cleaned up automatically. Perfect for keeping things tidy!
#!/bin/bash
#SBATCH --job-name=experiment
#SBATCH --container-image='docker://nvcr.io#nvidia/pytorch:24.11-py3'
#SBATCH --gpus=1
#SBATCH --time=01:00:00
python my_experiment.pyPersistent Containers
This is a save and reuse approach for regular tasks. It's best for repeated jobs with the same environment, custom setups, or when you want faster job startup. The first time this runs, the container is downloaded and saved with the name pytorch_container. Future jobs with the same --container-name will reuse the existing container, starting much faster.
#!/bin/bash
#SBATCH --job-name=training_run
#SBATCH --container-image='docker://nvcr.io#nvidia/pytorch:24.11-py3'
#SBATCH --container-name=pytorch_container
#SBATCH --gpus=1
#SBATCH --time=01:00:00
python train_large_model.pyThis approach is a time-saver when you're running multiple related jobs or iterations of the same experiment!
4. Finding the Right Container
You can virtually run any publicly available container image from a container registry, but we strongly advise you to look for container images on the NGC (NVIDIA GPU Cloud) Catalog, which is available here: https://catalog.ngc.nvidia.com . When you find a container you'd like to use, look for the "Get Container" button on the top right corner of the page, and copy the image path for the tag you wish to use.
Browse to the container you want on NGC
Click "Get Container" (top right)
Copy the image path for your desired tag
Use it directly in your
--container-imageparameter
The screenshots below illustrate the process.
Accessing NGC Containers
You should be able to pick any container you'd like to use. Some of them are public, some of them are private. For private containers, you need to have an API Key setup on DGX Head. The next section will further elaborate on that. You're probably asking yourself now: how do I know a container image is private? If the container page shows the "NVIDIA AI Enterprise Essentials" tag, then that container is private.
Here's the thing: Enroot tries to authenticate you with the registry regardless of whether a container is public or private. So for public containers, there's a clever way to bypass this authentication step. To skip authentication for public containers, you can replace docker://nvcr.io/nvidia/ with docker://nvcr.io#nvidia/. Notice the # instead of / after nvcr.io - this tells Enroot "don't bother authenticating, just grab the public container!" If you need to access private registries or NGC private collections, then look at the next section.
In the meantime, these are some popular NGC containers for AI/ML:
nvcr.io/nvidia/pytorch:24.11-py3- PyTorch with CUDA supportnvcr.io/nvidia/tensorflow:24.11-tf2-py3- TensorFlow 2.xnvcr.io/nvidia/cuda:12.3-devel-ubuntu22.04- CUDA development environmentnvcr.io/nvidia/rapids:24.10-cuda12.0-runtime-ubuntu22.04- RAPIDS data sciencenvcr.io/nvidia/tritonserver:24.11-py3- Triton Inference Server
Setting Up Access to Private Containers
This is a straightforward step that should only be done once. First, you generate an API Key on NGC. Then, you copy that Key to your home folder on DGX On-Prem Head node.
Getting an NGC API Key
Login at NGC
Go to Setup → Generate API Key
Scroll down to “Legacy Keys” and click on “Generate Legacy Key”
Copy the API Key and keep it securely as it will be used in the next step
There are 2 different types of API Keys: Personal Keys and Legacy Keys. Make sure you create a Legacy API Key and not a Personal Key, otherwise this won’t work.
The screenshots below illustrate the process.
Now that you have generated the API Key, we just need to tell Enroot to use them.
Setting Up Enroot
First, connect to the DGX On-Prem cluster.
ssh your_netid@dgx-head01.its.albany.eduYou'll need to enter your NetID password. If you're off-campus, make sure you're connected to the VPN first. Now, copy the commands right next, replace YOUR_NGC_API_KEY with the Key you created in the previous step, paste it to the terminal and run them.
# Create credentials directory
mkdir -p ~/.config/enroot
# Add your NGC API key (get from NGC)
cat > ~/.config/enroot/.credentials << EOF
machine nvcr.io login \$oauthtoken password YOUR_NGC_API_KEY
EOF
# Secure the file
chmod 600 ~/.config/enroot/.credentialsIf you want to double check the .credentials file have your API Key, you can run cat ~/.config/enroot/.credentials and expect to see something like the following.
machine nvcr.io login $oauthtoken password YOUR_NGC_API_KEY5. Accessing Your Data in Containers
Containers have their own isolated filesystem, so you need to explicitly grant access to your files. The DGX On-Prem automatically mounts your home folder, but lab directories have to be manually mounted. So if you need access to other directories, just use the --container-mounts option.
#!/bin/bash
#SBATCH --container-image='docker://nvcr.io#nvidia/pytorch:24.11-py3'
#SBATCH --container-mounts=/network/rit/dgx/dgx_YOUR_LAB_NAME:/mnt/dgx_lab,/network/rit/lab/YOUR_LAB_NAME:/mnt/lab
# Now you can access:
# /network/rit/dgx/dgx_YOUR_LAB_NAME as /mnt/dgx_lab inside the container
# /network/rit/lab/YOUR_LAB_NAME as /mnt/lab inside the container
python train.py6. Interactive Development and Container Customization
Now that you know how to deploy containers and access your data, let's explore how to use them for interactive development. This is where containers really shine - you can experiment, install packages, and develop code without worrying about breaking anything!
Quick Interactive Sessions
Sometimes you just want to jump into a container and try things out. No need for a job script - srun gives you instant interactive access:
# Quick 1-hour interactive session with 1 GPU
srun --gpus=1 --time=01:00:00 \
--container-image='docker://nvcr.io#nvidia/pytorch:24.11-py3' \
--pty /bin/bash
# Now you're inside the container! Try:
python -c "import torch; print(torch.cuda.is_available())"Need to work with your lab's data? Just add the mount:
srun --gpus=1 --time=01:00:00 \
--container-image='docker://nvcr.io#nvidia/pytorch:24.11-py3' \
--container-mounts='/network/rit/dgx/dgx_YOUR_LAB_NAME:/mnt/dgx_lab' \
--pty /bin/bashPro tip: Use --container-name to avoid re-downloading the container every time:
srun --gpus=1 --time=01:00:00 \
--container-image='docker://nvcr.io#nvidia/pytorch:24.11-py3' \
--container-name='my_dev_env' \
--pty /bin/bashCustomizing Persistent Containers
Here's where things get really interesting. By default, containers are read-only - you can't install new packages or modify files. But with --container-writable, you can customize your environment and the changes persist!
# Start an interactive session with a writable container
srun --gpus=1 --time=01:00:00 \
--container-image='docker://nvcr.io#nvidia/pytorch:24.11-py3' \
--container-name='my_custom_pytorch' \
--container-writable \
--pty /bin/bash
# Now install whatever you need!
pip install wandb transformers datasets
apt update && apt install -y vim htop # Yes, you can install system packages too!
# Your packages are saved in the container
exitNext time you use --container-name='my_custom_pytorch', all your installed packages are still there:
#!/bin/bash
#SBATCH --job-name=custom_training
#SBATCH --container-name='my_custom_pytorch' # Uses your customized container
#SBATCH --gpus=1
#SBATCH --time=01:00:00
# Your custom packages are available!
python custom_train.pyImportant notes about writable containers:
Changes are saved to the named container, not the base image
Each user's named containers are separate (your 'my_custom_pytorch' is different from someone else's)
Writable containers use more disk space - clean up old ones you're not using
System package installations (apt/yum) might need
--container-writableeven in interactive mode
Development Workflows
Here's a typical development workflow that many users find effective:
Start with exploration:
# Try out a new framework interactively
srun --gpus=1 --time=01:00:00 \
--container-image='docker://nvcr.io#nvidia/jax:24.10-py3' \
--pty /bin/bashCustomize for your project:
# Create a custom environment with your packages
srun --gpus=1 --time=01:00:00 \
--container-image='docker://nvcr.io#nvidia/pytorch:24.11-py3' \
--container-name='projectX' \
--container-writable \
--pty /bin/bash
# Inside the container:
pip install -r /home/myuser/projectX/requirements.txtDevelop and test interactively:
# Mount your code and test changes quickly
srun --gpus=1 --time=01:00:00 \
--container-name='projectX' \
--container-mounts='/network/rit/dgx/dgx_YOUR_LAB_NAME:/mnt/dgx_lab' \
--pty /bin/bash
# Edit locally, test in container
cd /mnt/dgx_lab
python test_model.pySubmit batch jobs:
#!/bin/bash
#SBATCH --job-name=projectX
#SBATCH --container-name='projectX'
#SBATCH --container-mounts='/network/rit/dgx/dgx_YOUR_LAB_NAME:/mnt/dgx_lab'
#SBATCH --gpus=1
#SBATCH --time=01:00:00
cd /mnt/dgx_lab
python train_full_model.pyThis workflow keeps development fast and interactive while ensuring your production runs use the exact same environment!
7. Essential SLURM and Pyxis Options
These are the most common tags you will likely be using, but feel free to browse the Pyxis Documentation.
Tag | Description |
|---|---|
| The image to use for the container filesystem. Can be either a docker image given as an enroot URI, or a path to a squashfs file on the remote host filesystem. |
| Bind mount[s] inside the container. |
| Name to use for saving and loading the container on the host. Unnamed containers are removed after the slurm task is complete; named containers are not. If a container with this name already exists, the existing container is used and the import is skipped. |
| Execute the entrypoint from the container image. |
| Do not execute the entrypoint from the container image. |
| Make the container filesystem writable. |
| Make the container filesystem read-only. |
| Names of environment variables to override with the host environment and set at the entrypoint. By default, all exported host environment variables are set in the container after the entrypoint is run, but their existing values in the image take precedence; the variables specified with this flag are preserved from the host and set before the entrypoint runs. |