Running Jobs in Containers#
Spur can run a job inside an OCI container image, backed by a squashfs snapshot
of the image. You import an image once with spur image, then pass container
flags to spur submit (Slurm sbatch) or spur run (Slurm srun) to
run your job inside it. This page covers importing images, running container
jobs, and executing extra commands inside a running one.
Importing Images#
spur image import <ref> fetches an image and packs it into a zstd squashfs
.sqsh file (this needs squashfs-tools installed). The reference can be a
registry image or a local container source:
spur image import ubuntu:22.04 # registry
spur image import registry.example.com/pytorch:latest # registry with host
spur image import docker://myapp:latest # registry via docker transport
spur image import dockerd://myapp:latest # local Docker daemon
spur image import podman://myapp:latest # local Podman storage
List, export, and remove imported images:
spur image list
spur image export mycontainer -o /tmp/mycontainer.sqsh
spur image remove mycontainer
Images are stored in the first of these directories that is usable:
$SPUR_IMAGE_DIR, then /var/spool/spur/images, then ~/.spur/images.
At submit time Spur resolves a bare image name (e.g. busybox) to an absolute
.sqsh path in these directories when it finds one — which works when the
login and compute nodes share a filesystem. Otherwise the name is passed through
for the node agent to resolve.
Running a Container Job#
Add container flags to sbatch or srun. The job’s script (or command)
runs inside the container.
Flag |
Description |
|---|---|
|
Image to run in. A registry reference, a stored name, or a |
|
Bind-mount a host path into the container. Repeatable; append |
|
Working directory inside the container. |
|
Persist the container across jobs. Required for |
|
Not yet implemented — rejected at submission. (A read-only container root is not enforced yet; the flag is refused rather than silently ignored.) |
|
Mount your home directory into the container. |
|
Set an environment variable inside the container. Repeatable. |
|
Shell command run inside the container immediately before the job script
( |
|
Map the job user to root inside the container. |
A GPU training job with a read-only data mount:
sbatch --container-image registry.example.com/pytorch:latest \
--container-mounts /data:/data:ro \
--gres=gpu:8 train.sh
An interactive shell in a container with your home directory mounted:
srun --container-image ubuntu:22.04 --container-mount-home bash
Environment Inside the Container#
A container job starts from the image’s own environment — the config.Env that
docker run would apply — and layers the job’s environment on top of it. Spur
records the image config at spur image import time, so images imported by an
older Spur have nothing recorded and keep starting from the job environment
alone; re-import them to pick up their environment.
Later entries win:
the image’s
config.Envthe job environment (
--export, which defaults toALL)--container-env KEY=VALadmin
environ.dfiles and hook variables
PATH and LD_LIBRARY_PATH are the exception: instead of being replaced,
the image’s entries and the job’s are joined, image first, so that the programs
and libraries shipped in the image stay reachable while additions from the
submitting host still apply. An explicit --container-env PATH=... replaces
the result outright.
This is what lets an image’s own tooling run without spelling out its paths:
# torchrun lives on the image's PATH (/opt/venv/bin), not the host's
sbatch --container-image registry.example.com/pytorch:latest train.sh
Containerized Job Steps#
srun steps run inside a container too, not just batch jobs. The container
flags above apply to a step’s srun and behave as follows:
Step inside a containerized allocation. When the batch job was submitted with
--container-image(sbatch --container-image ...), a nestedsrunstep enters that already-running container — it shares the job’s image, mounts, and GPU devices. This is the standard Slurm + Pyxis pattern used by multi-node launchers.Step with its own image. A standalone
srun --container-image(or ansallocallocation followed bysrun --container-image) creates a fresh container for that step. Different steps in one allocation can use different images and mounts.
Allocate first, then run steps from inside the allocation shell. By default a
single step fans out one container per allocated node; -N or -w limits
container creation or entry to the selected step nodes:
salloc -N2 --gpus-per-node=8
# ...now inside the allocation shell:
srun --container-image trainer.sqsh <command> # one container per node
srun -N1 --container-image trainer.sqsh <command> # one allocated node
srun -w node002 --container-image trainer.sqsh <command> # exact node
Note
-N and -w only choose which allocated nodes run the step. They do not
resize the job-level CPU, memory, or GPU cgroup on those nodes.
GPU allocation, bind mounts, and cancellation apply per step: a cancelled step tears down its container and cleans up its rootfs without affecting the rest of the allocation.
Interactive steps (srun --pty) run inside the container too — you get a real
terminal inside the container rather than on the host. --pty still ignores
--nodes and uses only the first -w name. The step uses its own
--container-image when given one, otherwise a nested srun --pty inherits
the image from its containerized sbatch job:
salloc -N1 --gpus-per-node=8
# ...now inside the allocation shell:
srun --pty --container-image trainer.sqsh bash # interactive shell in the container
# or attach an interactive shell to a running containerized job
# (enters its container via nsenter):
sbatch -N1 --container-image trainer.sqsh long_job.sh
srun --jobid=<id> --overlap --pty bash # same container, interactive
A --pty step nested inside a containerized sbatch job enters
the parent’s running container (like any other nested step) rather than building
a fresh one.
Exec Into a Running Container Job#
spur exec <job_id> <command...> runs a one-shot command inside a job’s
running container. Output is buffered — this is not an interactive terminal.
spur exec 1024 rocm-smi
spur exec 1024 -- ls -la /workspace
Tip
For an interactive session inside a running job, use srun --jobid <id>
--overlap or spur attach instead — spur exec has no
TTY mode.