skip to content

In Jenkins, what does the controller do compared with an agent, and why is running build steps on the built-in node treated as an anti-pattern?

level: middleimportance: must knowfreq 68%

answer

  1. orchestrator versus executor
  2. what sits in JENKINS_HOME
  3. build code is arbitrary code
  4. secrets and the key decrypting them, one directory
  5. set the built-in node to zero executors

basics

~20 s

The Jenkins controller schedules builds, serves the UI and owns JENKINS_HOME with all job config and credentials; agents execute the steps. Building on the controller gives arbitrary build code read access to that home directory, so its executors are set to zero.

solid answer

~50 s

A Jenkins controller is the orchestrator: it hosts the web UI, evaluates the Jenkinsfile, schedules queue items onto executors, and owns `JENKINS_HOME`, which contains job configuration, build history, installed plugins, the credentials store and the master key that decrypts it. An agent is a machine running the agent process that connects to the controller — inbound over a WebSocket or the older JNLP port, or outbound over SSH — and provides executors that actually run `sh`, `bat` and other steps in a workspace on that machine. Running builds on the controller's own built-in node collapses that separation. Build code is arbitrary code, so a `sh` step on the controller can read `JENKINS_HOME/secrets/` and `credentials.xml` and decrypt every stored credential, edit job configuration, or exhaust the memory and disk the controller needs to stay up. The standard hardening is to set the built-in node's executor count to zero so nothing can be scheduled there, and to route all work to labelled agents.

go deeper

for a junior

Know the two roles by name: the controller schedules and serves the UI, agents run the actual build steps in their own workspaces. Say that builds belong on agents, not on the controller.

for a middle

Explain what JENKINS_HOME holds and why a shell step running beside it defeats credential scoping. Name the fix — zero executors on the built-in node — and describe inbound versus outbound agent connections.

for a senior

Demonstrate the incident view: a build OOMing the controller, a Jenkinsfile reading secrets/master.key, workspaces filling the controller disk. Pair the zero-executor fix with agent-to-controller access control and ephemeral agents.

for a principal

Own the trust boundary. Decide who may edit Jenkinsfiles, which agent pools may hold which credentials, whether one controller serves all teams or each team gets its own, and how you would prove the controller runs no untrusted code.

## Two roles, one system Jenkins is a distributed build system with a deliberately asymmetric topology. Exactly one **controller** runs the Jenkins web application. It renders the UI, exposes the REST API, stores configuration, evaluates pipeline definitions (the Jenkinsfile is interpreted on the controller, step by step), maintains the build queue, and decides which queue item goes to which executor. Around it sit any number of **agents**: separate machines, VMs, containers or pods running the Jenkins agent process, each offering some number of **executors**. An executor is one slot for one build at a time; a `sh` step runs on the agent, in a workspace directory on that agent's disk. The controller and agent talk over a bidirectional channel. Two connection directions exist. **Inbound** agents dial the controller — historically over the JNLP TCP port, and since Jenkins 2.217 usually over WebSocket on the normal HTTP(S) port, which is what makes agents behind NAT and in Kubernetes practical. **Outbound** agents are launched by the controller over SSH. Either way, once connected, the controller sends the agent serialized work and receives results back. ## What lives on the controller `JENKINS_HOME` is the whole state of the installation on one directory: - `jobs/<name>/config.xml` — every job definition, and `builds/` with the history and logs. - `plugins/` — installed plugin code. - `credentials.xml` — the credentials store, encrypted. - `secrets/master.key` and `secrets/hudson.util.Secret` — the key material that decrypts it. - `users/`, `nodes/`, `secrets/initialAdminPassword` and the rest of the configuration surface. The critical point: the encrypted credentials **and** the key that decrypts them sit in the same directory tree. Encryption at rest protects a stolen backup; it does not protect against a process running on the controller with read access to that directory. ## Why building on the built-in node is an anti-pattern Jenkins ships with a built-in node (labelled `built-in`; before Jenkins 2.307 it was called the master node) that can, by default in older installations, execute builds directly on the controller machine. That is convenient and dangerous, for three separate reasons. **Security.** A pipeline step is arbitrary code written by whoever can edit a Jenkinsfile. Running on the controller, `sh 'cat $JENKINS_HOME/secrets/master.key'` is enough to begin decrypting the entire credentials store — including credentials that Jenkins would never have bound into that job. It can also rewrite `config.xml` files to grant itself permissions, or install a plugin. The credential scoping and per-job binding you carefully configured become advisory. **Stability.** The controller must stay responsive for every job, every user and every agent connection. A build that allocates a 12 GB heap, fills the disk with a Docker layer cache, or forks a thousand processes takes down the orchestrator for everyone, not just its own build. Workspaces on the controller also grow inside `JENKINS_HOME`, competing for the same disk as build history. **Scalability.** One controller cannot grow horizontally. Pushing execution onto agents is what lets you add capacity, offer several platforms, and give each build a clean machine. ## The standard hardening ```groovy // Anti-pattern: pins the pipeline to the controller pipeline { agent { label 'built-in' } stages { stage('Build') { steps { sh 'make' } } } } ``` The mitigations, in order of value: 1. **Set the built-in node's number of executors to 0** (Manage Jenkins → Nodes → Built-In Node → Configure). Nothing can be scheduled there any more; queue items wait for a real agent. This is the single most-cited Jenkins hardening step. 2. **Keep Agent → Controller Access Control enabled.** This subsystem restricts which commands and file paths an agent may ask the controller to execute or read over the channel — it is what stops a compromised agent from reaching back into `JENKINS_HOME`. 3. **Label agents and route deliberately**, so jobs express what they need (`linux`, `docker`, `windows`) rather than landing wherever there is capacity. 4. **Prefer short-lived agents** — a container or pod per build — so a build cannot leave state behind for the next one. Persistent agents accumulate caches, credentials in `~/.docker/config.json`, and half-finished checkouts. ## The honest exception A handful of things legitimately run on the controller because they have nowhere else to run: the pipeline's own flow logic (the Groovy CPS interpreter is a controller-side execution), `stash` storage, and lightweight steps in a pipeline-level `post`. That is why `agent none` does not mean "run on the controller" — flow control has always been controller-side, and it is the *workspace* work you are keeping off the box. ## How this comes up in interviews Usually as a scenario: "your Jenkins gets slow and occasionally OOMs during nightly builds" or "a developer exfiltrated production credentials through a Jenkinsfile — how?" Both answers run through the same split. Say what the controller owns, say that build code is untrusted code, and name the zero-executor fix.

  • If credentials.xml is encrypted, why is read access to JENKINS_HOME still enough to compromise them?
    Because the decryption key lives in the same directory. `secrets/master.key` and `secrets/hudson.util.Secret` are what Jenkins itself uses to decrypt `credentials.xml`, so anything that can read the tree can reproduce that decryption offline. Encryption at rest defends a stolen backup of the entries alone; it does not defend against code executing on the controller host.
  • How do inbound and outbound agent connections differ, and when do you need inbound?
    Outbound means the controller opens an SSH connection to the agent and launches the process, which needs the controller to reach the agent and hold an SSH key. Inbound means the agent dials the controller, over WebSocket on the normal HTTP port in modern versions or the older JNLP TCP port. You need inbound whenever the agent is behind NAT, in a container platform, or in a network the controller cannot reach — which is the usual case for Kubernetes and cloud agents.
  • What does Agent to Controller Access Control protect against, given the controller is the trusted side?
    It defends the controller from its agents. Without it, a step running on a compromised or untrusted agent can ask the controller to execute commands or read files over the channel. The subsystem whitelists which controller-side operations and file paths agents may request, so a hostile agent cannot pivot into `JENKINS_HOME`.

saying these in an interview costs you the question

  • Claiming encrypted credentials.xml makes controller builds safe
  • Thinking the controller runs steps for jobs with no agent match
  • Treating the built-in node as just another agent
  • Saying agents hold the credentials store
  • Calling controller execution merely a performance problem

context