Jenkins agents on demand: inbound and SSH agents, Kubernetes pods and cloud VMs
A Jenkins agent is a Java process that runs builds for the controller. You either let the controller start it over SSH, or start it yourself with java -jar agent.jar -url ... -secret ... -name ... -webSocket. For agents on demand, the Kubernetes plugin starts one pod per build and the EC2 plugin starts VMs that match a label, then removes them when the work finishes.
Stop building on the controller
A fresh Jenkins install runs builds on the built-in node. Jenkins' own security documentation says builds there "have the same level of access to the controller file system as the Jenkins process". A Jenkinsfile on any branch can then read credentials.xml, the secrets directory and every job's configuration. Builds also compete with the web UI and the queue for CPU and memory.
Go to Manage Jenkins, Nodes, open Built-In Node, set Number of executors to 0 and save. Every build after that needs an agent. Leave the Agent to Controller access control enabled; it has been mandatory since Jenkins 2.326 and blocks a compromised agent from sending commands to the controller.
Prerequisites
- A Jenkins controller with a correct Jenkins URL under Manage Jenkins, System. Inbound agents and the Kubernetes plugin hand this URL to agents.
- Java on every agent. Current weekly and LTS releases (2.555.1 LTS from April 2026) require Java 21 or 25 for the controller and all agents. Builds can still use any JDK you install for the project.
- For SSH agents: network access from the controller to port 22 on the agent. For inbound agents: outbound HTTPS from the agent to the controller (WebSocket), or access to the controller's inbound TCP port.
Inbound agents with agent.jar
Inbound agents (once called JNLP agents) connect out to the controller, so they work behind NAT and firewalls. The old Java Web Start launch is gone; you start them from the command line.
- In Manage Jenkins, Nodes, New Node, create a Permanent Agent. Set a remote root directory such as
/home/jenkins/agent, labels, and Launch method: Launch agent by connecting it to the controller. Tick Use WebSocket. - Save, then open the node page. Jenkins shows the agent's secret and the exact command.
- On the agent host, download
agent.jarfrom your controller and start it:
sudo useradd -m -s /bin/bash jenkins
sudo -iu jenkins
curl -fsSLO https://jenkins.example.com/jnlpJars/agent.jar
echo "<secret from node page>" > secret-file
chmod 600 secret-file
java -jar agent.jar \
-url https://jenkins.example.com/ \
-secret @secret-file \
-name linux-01 \
-webSocket \
-workDir /home/jenkins/agent
-secret @file reads the secret from a file so it stays out of ps output and shell history. -webSocket tunnels the connection over the controller's normal HTTP(S) port. Without it, the agent connects to the TCP port set in Configure Global Security, Agents (50000 in the official controller image), and you must open that port.
Keep the agent running with a systemd unit:
[Unit]
Description=Jenkins inbound agent
After=network-online.target
[Service]
User=jenkins
WorkingDirectory=/home/jenkins
ExecStart=/usr/bin/java -jar /home/jenkins/agent.jar -url https://jenkins.example.com/ -secret @/home/jenkins/secret-file -name linux-01 -webSocket -workDir /home/jenkins/agent
Restart=always
[Install]
WantedBy=multi-user.target
The same agent runs in Docker with the official jenkins/inbound-agent image, configured through environment variables:
docker run -d --init --name jenkins-agent-01 \
-e JENKINS_URL=https://jenkins.example.com/ \
-e JENKINS_SECRET=<secret> \
-e JENKINS_AGENT_NAME=linux-01 \
-e JENKINS_WEB_SOCKET=true \
-e JENKINS_AGENT_WORKDIR=/home/jenkins/agent \
jenkins/inbound-agent:latest-jdk21
SSH agents
With SSH agents the controller does the work: it connects to the host, copies the agent program and starts it. You need the SSH Build Agents plugin, which the setup wizard installs by default.
- Generate a key pair and put the public key in
~jenkins/.ssh/authorized_keyson the agent host. For a container, thejenkins/ssh-agentimage accepts it as an environment variable:docker run -d --name ssh-agent-01 -p 2222:22 \ -e "JENKINS_AGENT_SSH_PUBKEY=ssh-ed25519 AAAA... jenkins" \ jenkins/ssh-agent:alpine-jdk21 - In Manage Jenkins, Credentials, add an SSH Username with private key credential for user
jenkins. - Create a permanent agent with Launch method: Launch agents via SSH, the host and port, the credential, and a host key verification strategy. Use Known hosts file or Manually trusted key; Non verifying accepts a man-in-the-middle.
SSH suits a fixed set of machines on the controller's network. Inbound agents suit hosts the controller cannot reach and containers that start and stop on their own schedule.
Labels and label expressions
Labels are space-separated tags on a node, such as linux docker x86_64. Pipelines select agents with a label expression, using &&, || and !:
pipeline {
agent { label 'linux && docker && !arm64' }
stages {
stage('Test') {
steps { sh 'make test' }
}
}
}
Set a node's Usage to Only build jobs with label expressions matching this node for expensive or sensitive machines (GPU boxes, deploy agents). The default, Use this node as much as possible, lets unlabelled jobs land there too.
Kubernetes plugin pod templates
The Kubernetes plugin creates a pod for each agent and deletes it after the build. Configure a Kubernetes cloud under Manage Jenkins, Clouds with the cluster URL, a namespace and a service account that can create, list and delete pods. Tick WebSocket if the controller runs outside the cluster or behind an ingress. Container Cap limits how many agent pods run at once.
The plugin injects JENKINS_URL, JENKINS_SECRET and JENKINS_AGENT_NAME into a container named jnlp that runs jenkins/inbound-agent. You add tool containers next to it in the pipeline:
pipeline {
agent {
kubernetes {
yaml '''
apiVersion: v1
kind: Pod
spec:
containers:
- name: maven
image: maven:3.9.9-eclipse-temurin-21
command: ["cat"]
tty: true
resources:
requests:
cpu: "2"
memory: 4Gi
'''
defaultContainer 'maven'
}
}
stages {
stage('Build') {
steps { sh 'mvn -B verify' }
}
}
}
The cat command with tty: true keeps the tool container alive while Jenkins runs steps in it. Set resource requests so the scheduler and the cluster autoscaler can pick nodes with enough room. podRetention (never(), onFailure(), always()) keeps failed pods for debugging, and idleMinutes keeps a pod around for reuse, at the cost of losing the fresh-pod guarantee. Pipelines can reuse a shared template from the cloud configuration with inheritFrom.
EC2 and other cloud plugins
The Amazon EC2 plugin starts instances when jobs wait for a label and terminates them after an idle timeout (30 minutes by default). Under Manage Jenkins, Clouds you add an EC2 cloud with an IAM credential that can call ec2:RunInstances, ec2:TerminateInstances, ec2:DescribeInstances, ec2:CreateTags and iam:PassRole, then define AMI templates. Each template sets:
- Labels that jobs request, for example
ec2-linux-large. - Instance type, subnet, security group and an AMI with Java 21 installed.
- Instance Cap to bound spend.
- Number of Executors: set 1 to isolate builds from each other.
- Idle termination time and Maximum Total Uses.
- Spot instances for cheaper capacity that AWS can reclaim mid-build.
The plugin connects over SSH to Linux AMIs and over WinRM to Windows AMIs. Similar plugins exist for Azure VMs and Google Compute Engine. All of them rely on the controller's node provisioner, which watches queue load before it starts nodes. Your first build on a cold label waits for that decision and for the VM to boot.
One-shot agents
A one-shot agent runs one build and disappears. It removes the state that leaks between builds: workspace leftovers, Docker images, credentials written to disk, processes left running. Jenkins gives you three routes:
- Kubernetes plugin pods, which are one-shot by default.
- EC2 templates with one executor and Maximum Total Uses set to 1.
- Your own launcher that creates a node through the API or CLI, starts an inbound agent on a fresh VM and deletes the node after the build.
Pair one-shot agents with caches outside the agent (an artifact proxy, a remote build cache) or your builds get slower.
Security hardening
- Keep zero executors on the built-in node and Agent to Controller access control on.
- Store agent secrets in files with mode 600 and pass them as
-secret @file. - Separate agents by trust with labels and Only build jobs with label expressions matching this node. Pull requests from forks should never reach an agent that holds deploy credentials.
- In multibranch jobs, keep fork pull request trust restricted to users with write access. For untrusted forks, Jenkins builds with the Jenkinsfile from the target branch instead of the fork's copy.
- Avoid mounting the host Docker socket into agents; anyone who can edit a Jenkinsfile then owns the host.
Troubleshooting
Agent fails with a handshake or 404 error on connect
The agent tried TCP while the controller expects WebSocket, or the reverse. Match the -webSocket flag to the node setting, and check that a reverse proxy forwards WebSocket upgrade headers.
Agent connects but the controller says the URL is wrong
The Jenkins URL in system settings points to an internal hostname. Set it to the address agents can reach.
Java version errors on start
Your agent runs an older Java than the controller requires. Install Java 21 or 25 on the agent, or use a jdk21 image tag.
Kubernetes pods stay Pending
The pod requests more CPU or memory than any node can give, or the namespace quota is full. Check kubectl describe pod and the Container Cap.
SSH agent rejects the host key
The host key changed after a rebuild. Update known_hosts on the controller, or switch to a strategy that fits rebuilt hosts.
Cost trade-offs
Jenkins charges nothing per agent; the cost is all infrastructure and time. Static agents bill around the clock and accumulate drift. On-demand agents bill only while they run, but add boot latency and a cloud plugin to configure, upgrade and debug. The Kubernetes route also means owning a cluster, its autoscaler and node images. Spot instances cut EC2 cost but retry builds after interruptions. Count the hours your team spends on the controller and agent images when you compare options.
FAQ
Is JNLP still supported in Jenkins?
The Java Web Start launcher is gone. Inbound agents still exist: you start them with java -jar agent.jar and connect over WebSocket or the TCP agent port.
Should Jenkins agents use WebSocket or TCP?
WebSocket, in most setups. It needs a single connection over the port your controller already serves, so you avoid opening port 50000.
Can Jenkins create an agent per build?
Yes. The Kubernetes plugin does it by default, and the EC2 plugin does it with Maximum Total Uses set to 1.
Does the Jenkins controller need executors?
No. Set the built-in node to zero executors; the controller schedules builds and serves the UI.
Related guides: TeamCity build agents, Buildkite agents and GitLab CI custom runners.
Jenkins agents with cirunner.dev
cirunner.dev provisions a fresh VM per job, registers it with your CI using a token you provide, scales with the queue and destroys the machine after the job. You choose CPU, RAM, x86_64 or arm64, region, base image and an optional GPU, with usage-based pricing per compute minute.
The service is in early access, launching with GitHub Actions and GitLab CI. Jenkins is on the roadmap; vote for it on the early-access form.
Skip the runner fleet
cirunner.dev boots a fresh VM for every Jenkins job, registers it, and destroys it when the job ends. Jenkins is on our roadmap. Vote for it on the early-access form.
Join early access