Building a distributed Jenkins architecture: why the controller must not run production builds, connecting permanent SSH agents, dynamic Docker agents, and Kubernetes agents with ephemeral pods.

In episode 2 you wrote your first Jenkinsfile with agent any — and at that moment, the build ran anywhere. The question now is: where should that build actually run? The answer is the subject of this episode: distributed architecture. A real Jenkins does not run builds on the controller; it spreads work across many agents — permanent and dynamic alike.
This episode might be the one with the biggest impact on the maturity of your architecture. We will cover the most important security principle in all of Jenkins (the controller never runs production builds), then configure three types of agents: permanent SSH agents, dynamic Docker agents, and dynamic Kubernetes agents, as well as using labels to direct work to the right agent.
This is rule number one in the world of Jenkins. The two main reasons:
npm install, pip install, or scripts inside the repo. Code running on the controller means that code runs with the access rights of the Jenkins configuration, credentials, and the entire system. A single compromised repository is enough to destroy everything.Warning
The principle is absolute: the controller is the orchestra conductor, not the musician. It manages, schedules, and monitors — but never plays builds. Always direct builds to agents, no matter how small the job. Even a "harmless" job can become an attack vector.
Before configuring, understand three terms that are often mixed up:
All nodes are managed from Manage Jenkins → Nodes (the built-in node named Built-In Node is the controller itself — which we must avoid for production builds). Let's discuss three ways to connect worker nodes.
The most classic way: connecting a separate Linux VM as a fixed agent. The controller logs into the VM via SSH and runs builds there. First, prepare the user and SSH key on the worker VM:
sudo useradd -m -s /bin/bash jenkins
sudo -u jenkins ssh-keygen -t ed25519 -C "jenkins-agent" -f /home/jenkins/.ssh/id_ed25519 -N ""
sudo -u jenkins ssh-copy-id jenkins@agent-serverNext, install the SSH Build Agents plugin, then in the UI: Manage Jenkins → Nodes → New Node. Fill in the node name, number of executors, Remote root directory (e.g. /home/jenkins/workspace), Labels (e.g. linux), and for Launch method choose Launch agents by SSH, filling in the VM host and SSH key credentials. Once connected, the node appears in the Nodes list with online status.
SSH agents suit fixed needs: a build machine that must always exist, for example a runner with special software that is expensive to recreate.
The second approach is far more modern: don't keep an agent, create one per job. The Docker plugin lets the controller spin up a temporary container from an image, run the job inside it, then destroy the container when done. Clean, isolated, and without leftovers — you can even watch it with docker ps while the job runs.
The configuration: Manage Jenkins → Clouds → New cloud → Docker, fill in Docker Host URI (e.g. unix:///var/run/docker.sock), then define the Docker Image to be used as the agent (e.g. jenkins/inbound-agent:jdk17) and give the template Labels.
After that, the pipeline can target this cloud through a label:
pipeline {
agent { label 'docker-runner' }
stages {
stage('Build') {
steps {
echo 'Job ini berjalan di dalam kontainer Docker sementara'
}
}
}
}When a job is triggered, Jenkins pulls the image, runs the container, and places the job inside it. When done, the container is removed — no accumulating workspaces, no machines to maintain. This is a great fit for build isolation on the same machine.
The most scalable approach: use a Kubernetes cluster as the source of agents. The Kubernetes plugin lets Jenkins spin up ephemeral pods every time a job arrives — a pod containing a JNLP container (inbound agent) plus helper build containers such as Maven or Node. When the job finishes, the pod is destroyed automatically.
Once the plugin is installed and the Kubernetes cloud is configured (cluster URL, kubeconfig credentials, namespace), Jenkins can spin up a pod like this:
apiVersion: v1
kind: Pod
spec:
containers:
- name: jnlp
image: jenkins/inbound-agent:jdk17
- name: maven
image: maven:3.9-eclipse-temurin-17
command: ["cat"]
tty: trueThe pipeline can then select this pod via a label or an agent directive referencing the pod template. The big advantage: build capacity follows cluster capacity. When many jobs arrive, Kubernetes spins up as many pods as needed; when idle, the pods disappear — no idle cost.
We have seen the agent pattern using the docker-runner label several times. Labels are how Jenkins groups nodes and determines which nodes may run a given job. A node can have many labels, and a job can require a combination of labels:
| Form | Meaning |
|---|---|
label 'linux' | Runs on a node labeled linux |
label 'linux && docker' | Runs on a node with both linux and docker labels |
label 'k8s-build' | Runs on a pod template labeled k8s-build |
The rule of thumb: label by capability, not by hostname. The docker label is more meaningful than node-01 — because you can add new nodes with the same capability without changing the pipeline at all. This is also what keeps the Jenkins architecture flexible when machines are replaced or expanded.
Tip
Start with a simple label strategy: linux for SSH VMs, docker-runner for the Docker cloud, and k8s-* for Kubernetes pods. Don't create labels that are too specific per node — the goal is to hide physical details from the pipeline.
In episode 3 you have understood:
agent with the docker-runner label.The key takeaway to carry with you: the controller manages, work is sent to agents, and agents can appear and disappear dynamically. In episode 4 we will answer the next question — when the pipeline runs — by covering event triggers and execution automation: manual triggers, cron schedules, SCM polling, GitHub and GitLab webhooks, and Multibranch Pipelines. See you in episode 4!