Mastering Container Usage on DGX On-Prem

Mastering Container Usage on DGX On-Prem

Table of Contents

  1. Introduction

  2. Understanding the Technology Stack

  3. Container Deployment Strategies

  4. Finding the Right Container

  5. Accessing Your Data in Containers

  6. Interactive Development and Container Customization

  7. Essential SLURM and Pyxis Options

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_container

Enroot 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.py

SLURM + 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.py

The following diagram illustrates how these different parts interact to each other from a very high-level perspective.

mermaid-diagram-2025-09-24-103301.png

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.py

Persistent 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.py

This 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.

  1. Browse to the container you want on NGC

  2. Click "Get Container" (top right)

  3. Copy the image path for your desired tag

  4. Use it directly in your --container-image parameter

The screenshots below illustrate the process.

Browse to the container you want on NGC

 

Click Get Container and copy the Tag

 

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 support

  • nvcr.io/nvidia/tensorflow:24.11-tf2-py3 - TensorFlow 2.x

  • nvcr.io/nvidia/cuda:12.3-devel-ubuntu22.04 - CUDA development environment

  • nvcr.io/nvidia/rapids:24.10-cuda12.0-runtime-ubuntu22.04 - RAPIDS data science

  • nvcr.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

  1. Login at NGC

  2. Go to Setup → Generate API Key

  3. Scroll down to “Legacy Keys” and click on “Generate Legacy Key

  4. 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.

Scroll down to Legacy Keys

Now that you have generated the API Key, we just need to tell Enroot to use them.

Click on Generate Legacy Key

 

Setting Up Enroot

First, connect to the DGX On-Prem cluster.

ssh your_netid@dgx-head01.its.albany.edu

You'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/.credentials

If 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_KEY

5. 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.py

6. 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/bash

Pro 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/bash

Customizing 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 exit

Next 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.py

Important 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-writable even in interactive mode

Development Workflows

Here's a typical development workflow that many users find effective:

  1. 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/bash
  1. Customize 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.txt
  1. Develop 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.py
  1. Submit 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.py

This 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

Tag

Description

--container-image

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.

--container-mounts

Bind mount[s] inside the container.

--container-name

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.

--container-entrypoint

Execute the entrypoint from the container image.

--no-container-entrypoint

Do not execute the entrypoint from the container image.

--container-writable

Make the container filesystem writable.

--container-readonly

Make the container filesystem read-only.

--container-env

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.