Jenkins plugin to run dynamic agents in a Slurm cluster.
Similar to the Kubernetes plugin, automates the scaling of Jenkins agents running as Slurm jobs.
The plugin creates a Slurm job for each agent started, and cancels it after each build.
Agents are launched as inbound agents via WebSocket, so the compute node connects automatically to the Jenkins controller. The following environment variables are automatically injected into each agent job:
JENKINS_URL: Jenkins web interface URLJENKINS_SECRET: the secret key for authenticationJENKINS_AGENT_NAME: the name of the Jenkins agent
Tested with a sqsh image of jenkins/inbound-agent,
see the Docker image source code.
It is not required to run the Jenkins controller inside the Slurm cluster.
- Generic setup
- Usage
- Configuration reference
- Declarative pipeline
- Pyxis / Enroot container support
- Running without Pyxis / containers
- Folder-based cloud restrictions
- Features controlled using system properties
- Paths reference
- Troubleshooting
- Monitoring provisioning
- Building and testing
- Related projects
- A running Slurm cluster 24.11 or later
slurmrestdenabled with JWT authentication (Slurm REST quick start)- A Jenkins instance
- The Slurm plugin installed
- (Optional) Pyxis and Enroot for container support
Open the Jenkins UI and navigate to Manage Jenkins → Clouds → Add a new cloud → Slurm. Enter the Slurm REST URL and Jenkins URL appropriately.
Supported credentials:
- Secret File — a file containing the JWT token
Agents connect over WebSocket (HTTP/S) by default. This works whether Jenkins runs inside or outside the Slurm cluster, and simplifies setup when Jenkins is behind a reverse proxy. See JEP-222 for more.
Use the Test Connection button to verify communication from Jenkins to slurmrestd.
In the Job Templates section you can pre-configure reusable agent profiles. Each template maps a Jenkins label to a set of Slurm job parameters.
Key template fields:
- Name — identifier for the template
- Label — space-separated Jenkins node labels; jobs requesting any of these get this template
- Partition — Slurm partition (queue)
- CPUs per task —
--cpus-per-task - Memory per node — in MB
- Time limit — wall-clock minutes
- TRES per job — generic resources, e.g.
gres/gpu:a100:1 - Instance cap — maximum concurrent agents from this template
- Idle minutes — keep agent alive for reuse between builds
Clouds can be restricted to specific folders. In the cloud's advanced configuration, check Restrict pipeline support to authorized folders. Then add the cloud to the folder's configuration via Folder → Configure → Slurm Clouds.
The Slurm plugin allocates Jenkins agents as Slurm batch jobs.
Within each job there is always one process running the Jenkins inbound agent,
launched via srun -N1 -n1 to ensure a single agent connection per job.
The agent process runs either directly on the compute node or inside a Pyxis/Enroot container if container support is configured.
When multi-node allocations are requested (minimum_nodes > 1), the Jenkins agent runs on the
first node only, while your pipeline commands can utilise the full allocation using srun or MPI launchers.
Job templates configured in the UI declare a label.
When a freestyle or pipeline job requests node('some-label'), the Slurm cloud allocates a new
job to serve the agent.
The global template definition is the easiest migration path for existing jobs that already use labels.
New pipelines should use the slurm declarative agent or slurmJobTemplate step instead.
The slurmJobTemplate step defines an ephemeral job template scoped to the pipeline block.
It is removed when the block exits.
slurmJobTemplate(cloud: 'my-cluster', json: '''
{
"job": {
"partition": "gpu",
"cpus_per_task": 8,
"tres_per_job": "gres/gpu:1"
}
}
''') {
node(SLURM_LABEL) {
sh 'nvidia-smi'
}
}Find more examples in the examples directory, including static-node-migration.json for migrating long-lived SSH agents to Slurm templates.
Templates can be configured via the Jenkins UI, JCasC YAML, or inline pipeline JSON.
| Field | Description | Example |
|---|---|---|
name |
Template identifier | gpu-agent |
label |
Space-separated Jenkins labels | gpu ml cuda |
partition |
Slurm partition | gpu |
cpusPerTask |
CPUs per task | 8 |
memoryPerNode |
Memory in MB | 16384 |
timeLimit |
Wall-clock minutes | 240 |
tresPerJob |
Generic resources (GPUs, etc.) | gres/gpu:a100:1 |
instanceCap |
Max concurrent agents from this template | 5 |
idleMinutes |
Minutes to keep idle agent before terminating | 10 |
nodeUsageMode |
NORMAL or EXCLUSIVE |
EXCLUSIVE |
defaultPartition |
Fallback partition when template omits one | compute |
agentTimeoutMinutes |
Minutes to wait for agent to come online | 5 |
The json field accepts a structure that maps directly to the Slurm REST API job_desc_msg:
{
"job": {
"partition": "gpu",
"account": "myaccount",
"cpus_per_task": 16,
"memory_per_node": { "set": true, "number": 32768 },
"time_limit": { "set": true, "number": 120 },
"tres_per_job": "gres/gpu:gfx942:4",
"minimum_nodes": 1,
"required_nodes": ["node1"],
"constraints": "avx2"
},
"pyxis": {
"containerImage": "/home/user/jenkins-agent.sqsh",
"containerMountHome": true,
"containerWritable": false
}
}Simple integer forms are also accepted for memory_per_node (MB) and time_limit (minutes).
See docs/JSON_CONFIGURATION.md for the full field reference.
jenkins:
clouds:
- slurm:
name: "my-cluster"
slurmRestApiUrl: "http://slurm-controller:6820"
credentialsId: "slurm-jwt"
defaultPartition: "compute"
maxAgents: 20
agentTimeoutMinutes: 5
jobTemplates:
- name: "cpu-default"
label: "linux"
partition: "compute"
cpusPerTask: 4
memoryPerNode: 8192
timeLimit: 120
instanceCap: 10
- name: "gpu-large"
label: "gpu ml"
partition: "gpu"
cpusPerTask: 16
memoryPerNode: 32768
tresPerJob: "gres/gpu:a100:4"
timeLimit: 480
instanceCap: 2Agents can be defined inline using the slurm declarative agent:
pipeline {
agent {
slurm {
cloud 'my-cluster'
json '''
{
"job": {
"partition": "gpu",
"cpus_per_task": 8,
"tres_per_job": "gres/gpu:1"
}
}
'''
}
}
stages {
stage('Train') {
steps {
sh 'python train.py'
}
}
}
}Or reference a static template by label:
pipeline {
agent {
label 'gpu' // resolved by the configured gpu-large template
}
stages {
stage('Train') {
steps {
sh 'python train.py'
}
}
}
}pipeline {
agent none
stages {
stage('Compile') {
agent {
label 'linux'
}
steps { sh 'make' }
}
stage('GPU test') {
agent {
slurm {
cloud 'my-cluster'
json '{"job": {"partition": "gpu", "tres_per_job": "gres/gpu:1"}}'
}
}
steps { sh 'pytest tests/gpu/' }
}
}
}If your Slurm cluster runs Pyxis, the plugin can run agents inside an Enroot container image.
Add a pyxis block to the job JSON:
{
"job": {
"partition": "gpu",
"cpus_per_task": 8,
"tres_per_job": "gres/gpu:1"
},
"pyxis": {
"containerImage": "/path/to/jenkins-agent.sqsh",
"containerMountHome": true,
"containerWritable": false,
"containerMounts": "/data:/data",
"containerRemap": true
}
}| Field | Type | Description |
|---|---|---|
containerImage |
string | Path to .sqsh container image |
containerMountHome |
boolean | Mount the user's home directory |
containerMounts |
string | Additional bind mounts (host:container) |
containerWorkdir |
string | Working directory inside container |
containerWritable |
boolean | Make the container layer writable |
containerRemap |
boolean | Remap root user in container |
containerName |
string | Name for the container instance |
Clusters without Pyxis can run the inbound Jenkins agent
directly on compute nodes. You must provide Java 17+ and agent.jar on each node (or
enable download-at-runtime).
| Level | Where | Use for |
|---|---|---|
| Cloud | Manage Jenkins → Clouds → Slurm → Default Agent Launch | Site-wide java_path / jar_path |
| Template | Job template → Agent Launch (without container) | Per-label overrides |
| Pipeline | Declarative properties or JSON agent block |
Per-stage overrides |
Later levels override earlier ones. Pyxis, when configured, takes precedence over native launch.
agent {
slurm {
cloud 'my-cluster'
partition 'compute'
workingDir '/tmp/jenkins'
javaPath '/opt/jenkins/jdk-17/bin/java'
jarPath '/opt/jenkins/agent.jar'
}
}agent {
slurm {
cloud 'my-cluster'
json '''
{
"job": {
"partition": "compute",
"current_working_directory": "/tmp/jenkins",
"cpus_per_task": 4
},
"agent": {
"java_path": "/opt/jenkins/jdk-17/bin/java",
"jar_path": "/opt/jenkins/agent.jar"
}
}
'''
}
}Set "download_jar": true in the agent block (or enable Download Agent JAR in the UI)
to fetch agent.jar from the controller into the job workdir on first run.
| Field | JSON key | Description |
|---|---|---|
javaPath |
java_path |
Java executable on compute nodes (default: java) |
jarPath |
jar_path |
Path to pre-installed agent.jar |
downloadJar |
download_jar |
Download JAR from controller at job start |
setupScript |
setup_script |
Shell commands before launch (e.g. module load java/21) |
See docs/NATIVE_AGENT_SETUP.md for the admin checklist (Java 17,
agent.jar, workdir, network). E2E scripts: scripts/e2e/prepare-native-agent.sh,
scripts/e2e/install-jdk17-remote.sh, example pipeline examples/native-agent-test.groovy.
Individual clouds can be restricted so that only jobs in specific folders may use them.
- In the cloud's Advanced settings, check Restrict pipeline support to authorized folders.
- In the folder's Configure page, select the clouds that folder is permitted to use.
Jobs outside permitted folders are blocked at queue time by SlurmQueueTaskDispatcher.
See Features controlled by system properties for how to set these in Jenkins.
| Property | Default | Description |
|---|---|---|
io.jenkins.plugins.slurm.SlurmCloud.agentTimeoutMinutes |
5 |
Minutes to wait for an agent to connect before failing |
When a build provisions a Slurm agent, several independent path namespaces come into play: the Jenkins controller, the Slurm REST API, the Slurm job on a compute node, and the inbound agent process inside an optional Pyxis container. This section lists them in one place so you can map console output and cluster files to the right location.
Placeholders used below:
| Placeholder | Meaning |
|---|---|
{jenkins} |
Jenkins controller host (e.g. localhost:8080 for mvn hpi:run) |
{controller} |
Slurm controller hostname or IP |
{cloud} |
Slurm cloud name configured in Jenkins |
{template} |
Job template name |
{folder} |
Jenkins folder item name |
{job} |
Pipeline or freestyle job name |
{agent} |
Dynamic agent name ({cloud}-{template}-{timestamp}-{sequence}) |
{jobId} |
Numeric Slurm job ID from the build log |
{workdir} |
Template current_working_directory (default /tmp/jenkins) |
| Path | Purpose |
|---|---|
http://{jenkins}/jenkins |
Web UI (mvn hpi:run default) |
http://{jenkins}/jenkins/ |
REST API root; also the trailing-slash form agents expect in JENKINS_URL |
/jenkins/job/{folder}/job/{job}/ |
Job page URL |
/jenkins/job/{folder}/job/{job}/build |
Trigger a build (POST with CSRF crumb) |
/jenkins/job/{folder}/job/{job}/api/json |
Job metadata |
/jenkins/crumbIssuer/api/json |
CSRF crumb for scripted API calls |
work/jenkins/ |
Jenkins home directory created by mvn hpi:run (under the plugin checkout) |
target/slurm.hpi |
Built plugin artifact from mvn package |
The Jenkins URL field on the Slurm cloud is what gets passed to agents as JENKINS_URL.
It is resolved in this order: cloud configuration → Manage Jenkins → System → Jenkins URL
→ Jenkins root URL → http://localhost:8080/jenkins/ (dev fallback).
When Jenkins runs outside the cluster, this URL must be reachable from compute nodes over HTTP/WebSocket — often different from the URL you use in a browser on your laptop. See Agent reachability below.
| Path | Purpose |
|---|---|
http://{controller}:6820 |
Base URL configured on the Slurm cloud (Slurm REST URL) |
http://{controller}:6820/slurm/v0.0.42/ping/ |
Health check used by Test Connection |
http://{controller}:6820/slurm/v0.0.42/jobs/ |
Job submission (POST) and queries |
The plugin client appends /slurm/v0.0.42/... to the configured base URL automatically.
The template field Working Directory maps to the Slurm REST field
current_working_directory. It is where the batch script starts and where Slurm writes
stdout and stderr. Create this directory once on the compute nodes. The plugin does not
need a directory prepared for each agent.
For native launch the batch script creates a private directory and removes it when the job exits:
- Slurm WorkDir — the template directory
- Jenkins remote root (
remoteFS) and remoting-workDir—{workdir}/agents/{agent}
Build checkouts live under that private root. Two agents on the same node do not share
workspace/<job>/, so one build wiping its workspace cannot delete the other's files.
Pyxis launch keeps remoteFS as {workdir}. Remoting uses /tmp/{agent} inside the
container. A custom batch script is submitted unchanged, and remoteFS stays the template
directory, because the plugin does not create the private directory in that case.
| Setting | Default | Notes |
|---|---|---|
| UI / JCasC template | /tmp/jenkins |
Must exist and be writable on every compute node |
| Inline pipeline JSON | (none — you must set it) | Set "current_working_directory": "/tmp/jenkins" in the job object |
| Pipeline helper default | /tmp/jenkins-{cloud} |
Used when the plugin synthesizes a template from declarative JSON |
Do not point current_working_directory at paths that only exist on the Jenkins
controller (e.g. /var/jenkins_home) or at home directories owned by another service
account with restrictive permissions. If WorkDir is missing or not writable, the Slurm job
exits immediately and the agent never connects.
During a build, paths on the allocated node look like this (with default {workdir} =
/tmp/jenkins and agent my-cloud-gpu-agent-1712345678901):
{workdir}/ ← Slurm WorkDir (shared parent)
├── slurm-{jobId}.out ← Slurm stdout (shown in build log hints)
├── slurm-{jobId}.err ← Slurm stderr (if configured separately)
├── agent.jar ← cached here when Download Agent JAR is enabled
└── agents/
└── {agent}/ ← Jenkins remoteFS + remoting -workDir
├── remoting/ ← Remoting logs
└── workspace/
└── {folder}/
└── {job}/ ← Pipeline workspace (checkout, build steps)
The build console line Running on … in /path/to/workspace refers to
{workdir}/agents/{agent}/workspace/{folder}/{job} (or the freestyle equivalent).
Pyxis agents still use {workdir}/workspace/... inside the container.
Slurm job name equals the Jenkins agent name: {cloud}-{template}-{timestamp}-{sequence}.
When a template uses Pyxis, the batch script runs inside the container image. The plugin
hard-codes paths from the standard jenkins/inbound-agent
image layout:
| Path | Purpose |
|---|---|
/opt/java/openjdk/bin/java |
JVM used to start the agent |
/usr/share/jenkins/agent.jar |
Inbound agent JAR |
-workDir /tmp/{agent} |
Per-agent remoting directory (outside {workdir}) |
The container image itself is a cluster-local path (commonly a .sqsh file), e.g.
/path/to/jenkins-inbound-agent.sqsh, set in the template Pyxis section or pipeline JSON
pyxis.containerImage.
Optional Pyxis containerWorkdir overrides the working directory inside the container;
it does not replace {workdir} on the host for Slurm stdout or Jenkins remoteFS.
If Jenkins is on your laptop but agents run on the cluster, compute nodes must reach the cloud Jenkins URL, not necessarily the URL you open locally.
A common local-dev pattern (see scripts/e2e/):
| Location | URL / bind | Who uses it |
|---|---|---|
| Your machine | http://localhost:8080/jenkins |
Browser, REST scripts, mvn hpi:run |
Slurm controller (SSH -R) |
127.0.0.1:5000 → your :8080 |
Tunnel endpoint on the cluster side |
| Slurm cloud Jenkins URL | http://localhost:5000/jenkins/ |
Agents on compute nodes (via controller loopback + tunnel) |
Set SLURM_JENKINS_AGENT_URL in scripts/e2e/config.env to match the cloud configuration.
When Jenkins runs on the cluster network (see scripts/jenkins-instance/), set the cloud
Jenkins URL to the instance URL agents can reach directly, e.g. http://<host>:8282/.
No SSH reverse tunnel is required.
| Path | Purpose |
|---|---|
src/main/resources/openapi/slurm-v0.0.42.json |
OpenAPI spec; preprocessed before code generation |
target/generated-sources/ |
Generated Slurm REST Java client (recreated on each build) |
examples/ |
Sample pipeline definitions |
scripts/e2e/config.env.example |
Local e2e placeholders; copy to gitignored config.env |
scripts/jenkins-instance/ |
Docker Jenkins instance harness; see README.md in that folder |
In the build console look for the submitted job ID, e.g. Submitted Slurm job 12345.
If missing, the plugin failed before submission — check the Jenkins system log.
squeue --job 12345
scontrol show job 12345
sacct -j 12345 --format=JobID,State,ExitCode,NodeList- Check the agent log: Manage Jenkins → Nodes → <agent> → Log.
- Verify
slurmrestdis reachable from Jenkins. - Ensure the compute node can reach
JENKINS_URLover WebSocket. - Confirm the JWT token has not expired.
- Increase agent timeout via
agentTimeoutMinutesif node startup is slow. - If the Slurm job fails instantly, check Paths reference — especially
current_working_directoryon compute nodes and the cloud Jenkins URL agents use.
Add a Jenkins log recorder for
io.jenkins.plugins.slurm at FINE level to see template selection, job submission, and polling details.
The Slurm plugin integrates with the Cloud Statistics plugin (optional dependency — install it on the controller to use the UI and REST API).
Each dynamic agent is tracked as a provisioning activity when SlurmCloud creates a
TrackedPlannedNode. During launch, SlurmLauncher attaches Slurm-specific context to the
LAUNCHING phase:
| Event | cloud-stats level | Example attachment |
|---|---|---|
Job submitted to slurmrestd |
OK | Submitted Slurm job 413835 |
| Slurm state change | OK / FAIL | Slurm job 413835: PENDING (Priority) |
| Job PENDING longer than 60s | WARN | Slurm job 413835 still PENDING after 90s (Priority) |
| Agent connected | OK | Agent connected after 42s (Slurm job 413835) |
| Launch failure / timeout | FAIL | Timeout waiting for agent… (job 413835, state PENDING) |
With cloud-stats installed: Manage Jenkins → Cloud Statistics shows recent provisioning activities, phases, and the attachments above.
GET /cloud-stats/api/json?depth=2
Filter for Slurm agents by trackedItem name or cloud name. Each activity exposes phases
with attachments (title, status, timestamp).
The Kubernetes plugin does not use cloud-stats; it exposes counters via jenkins-metrics. The Slurm plugin already uses cloud-stats for activity tracking — Phase A enriches LAUNCHING attachments with Slurm job IDs, states, and failure context. Future work may add jenkins-metrics counters for dashboard parity.
mvn clean package # produces target/slurm.hpi
mvn spotless:apply # fix formattingmvn test # all tests
mvn test -Dtest=SlurmCloudTest # single class
mvn test -Dtest='*Template*' # pattern matchmvn hpi:runThen open http://localhost:8080/jenkins and configure a Slurm cloud pointing at a real or mock slurmrestd.
Note: The build auto-generates a Slurm REST API Java client from
src/main/resources/openapi/slurm-v0.0.42.jsonvia theopenapi-generator-maven-plugin. Generated code lands intarget/generated-sources/.
- Kubernetes plugin — the inspiration for this plugin's architecture
- Slurm REST API documentation
- Pyxis (NVIDIA) — container support for Slurm
- jenkins/inbound-agent — base agent image
Refer to our contribution guidelines.
Licensed under MIT, see LICENSE.
