Distributed Builds at Scale in Jenkins
Learn how Jenkins spreads work across many machines: how controllers and agents cooperate, how builds find a place to run, how to scale up and down, and how to keep speed, cost, reliability and security in balance as your team grows.
A single machine can build a small project happily. Then the team grows. More developers push more code, tests multiply, projects need different operating systems, and suddenly the one machine is busy all day. Builds queue up. Developers wait forty minutes to learn that a small change is fine. Someone suggests buying a bigger machine, which helps for a month, and then the queue returns.
Jenkins solves this with distributed builds. Instead of doing all the work in one place, Jenkins keeps a small manager and sends the real work to many worker machines. You can add workers when the queue grows and remove them when it shrinks. This is the design behind installations that run tens of thousands of builds a day.
This guide starts from zero. Every new word is explained when it first appears, everyday pictures make ideas stick, and the later chapters cover what experienced engineers care about: how builds are scheduled, how to size a fleet using simple arithmetic, how to use containers and clouds for automatic scaling, how to protect the manager from overload, and how to keep costs and risks under control.
1What Are Distributed Builds?
A plain-language first look before any details.
Jenkins is a free, open-source automation server that builds, tests and delivers software. Its work is written as a pipeline, a recipe stored in a text file called the Jenkinsfile. A pipeline is divided into stages, such as Build, Test and Deploy, and each stage contains steps, the single actions Jenkins performs.
A distributed build setup splits Jenkins into two kinds of machines. The controller is the manager: it stores settings, schedules work, shows the web pages and keeps history. The agents are the workers: they run the commands that compile code, run tests and package software. Many agents can work at the same time, so many builds can run in parallel.
Picture a restaurant. One head chef takes orders, decides who cooks what, and keeps the books. Many line cooks do the actual cooking at their own stations. On a quiet night, a few cooks are enough. On a busy Saturday, the owner calls in extra cooks, and sends them home when the rush ends. The controller is the head chef, agents are the line cooks, and scaling is calling cooks in and out.
The controller should think, not cook. A healthy large Jenkins installation keeps the controller almost free of build work, and lets agents do the heavy lifting.
Distribution brings three gifts. Speed: many builds run at once, and one build can even split into parallel pieces. Variety: some agents run Linux, some Windows, some have special hardware, so each job runs where it fits. Isolation: a misbehaving build damages only its own agent, not the manager. The price is more moving parts to run, monitor and secure, which is what this guide is about.
2Why Distribution Exists
The problem it solves and how the idea grew.
The problem: one machine has limits
Every machine has a fixed number of processor cores, a fixed amount of memory and a fixed disk speed. When many builds run at once, they compete for these resources and all slow down. Worse, one runaway build can consume everything, including the resources Jenkins itself needs to stay responsive. The web page slows, the queue stalls, and people cannot even see what is wrong.
There is also a variety problem. A mobile team needs machines with phone tools. A data team needs machines with graphics cards. A company that ships on Windows and Linux needs both. One machine cannot be all of these.
One server does everything
Early Jenkins ran builds on the same machine that hosted the web pages. Simple, but it did not scale.
Controller and agents
Jenkins introduced worker machines, first called slaves and now called agents, so builds could run elsewhere.
Cloud-provisioned agents
Plugins let Jenkins request virtual machines on demand from cloud providers, and shut them down when idle.
Containers and Kubernetes
Agents became short-lived containers, started per build in seconds, with identical, clean environments.
Many controllers
Very large organizations run several controllers, one per team or business unit, to limit the blast radius of any single one.
3Core Concepts You Need First
A handful of small ideas that make everything else easy.
Controller
The main Jenkins server. It holds configuration, credentials, job definitions and build history, and decides which agent runs what.
Agent
A worker machine, virtual machine or container that runs build steps. It connects to the controller and waits for work.
Executor
A slot on an agent that can run one build at a time. An agent with four executors can run four builds at once. Executors are the unit of capacity in Jenkins.
Label
A tag attached to an agent, such as linux or gpu. Pipelines ask for a label, and Jenkins picks an agent that carries it.
Build queue
The waiting list of builds that have work to do but no free executor yet. Queue length and waiting time are the best health signals of a distributed setup.
Workspace
A folder on an agent where a build downloads code and writes output.
Cloud (provisioner)
A plugin that can create and destroy agents automatically, for example by asking a cloud provider for a virtual machine or a Kubernetes cluster for a pod.
| Term | Simple meaning | Why it matters at scale |
|---|---|---|
| Controller | The manager | Single point of coordination; must stay healthy |
| Agent | A worker | Where all real work should happen |
| Executor | A slot for one build | The unit of capacity |
| Label | A tag on an agent | Routes work to suitable machines |
| Queue | The waiting list | Wait time reveals shortage of capacity |
| Workspace | Working folder | Cleanup and caching decisions live here |
| Cloud | An automatic agent factory | Enables scaling up and down |
4Architecture and Components
The parts of a distributed Jenkins setup and how they fit together.
flowchart LR
DEV["Developers push code"] --> SCM["Source Control Server"]
SCM -->|"webhook"| CTRL["Jenkins Controller"]
CTRL --> Q["Build Queue"]
Q --> SCH["Scheduler matches labels"]
SCH --> P1["Static agent pool: Linux"]
SCH --> P2["Static agent pool: Windows"]
SCH --> P3["Elastic pool: containers or cloud VMs"]
P1 --> ART["Artifact storage"]
P2 --> ART
P3 --> ART
P1 --> CACHE["Shared caches and package proxies"]
P3 --> CACHE
P3 -->|"provision and remove"| CLOUD["Cloud or Kubernetes cluster"]
CTRL --> MON["Monitoring and alerts"]
Read the diagram from left to right. A push triggers the controller through a webhook. Work goes into the build queue. The scheduler matches each item to an agent that carries the right label, drawing from static pools of always-on machines or an elastic pool that expands through a cloud or a cluster. Agents fetch dependencies from shared caches and package proxies, and publish results to artifact storage. Monitoring watches the queue and the machines and raises alerts.
Controller
Schedules, stores history and serves the web interface. Should run no builds itself.
Agents
Run the builds. Can be permanent machines, virtual machines or short-lived containers.
Cloud plugins
Create agents when the queue grows and remove them when idle.
Artifact storage
Holds build outputs outside the controller, so history does not fill its disk.
Caches and proxies
Keep downloaded libraries and images close to agents, saving time and bandwidth.
Monitoring
Watches queue length, wait times, agent health and controller load.
5The Controller: What It Should and Should Not Do
Protecting the one component that everything depends on.
In a distributed setup, the controller is the single place where configuration, credentials and history live. If it slows down, every developer feels it. If it fails, no build can start. Keeping it healthy is the first rule of scaling.
The controller should
- Schedule builds and match them to agents.
- Serve web pages and the programming interface.
- Store configuration, credentials and history.
- Coordinate pipelines, lightly.
- Talk to the cloud to provision agents.
The controller should not
- Run compilations, tests or scripts as builds.
- Run heavy scripting logic inside pipelines.
- Store large artifacts and huge logs forever.
- Run untrusted code, ever.
- Share its machine with other busy services.
Set the built-in executors to zero
By default the controller has a built-in node that can run builds. On a shared installation you should set its number of executors to zero, so nothing runs there. This has two benefits: performance is protected, and security improves, because untrusted build code can no longer reach the machine that holds all the secrets.
The hidden work in pipelines
Even when all steps run on agents, the pipeline’s own logic, the small program that decides what happens next, runs on the controller. Simple pipelines make this cost trivial. But a pipeline that loops through thousands of items, parses large files or does heavy calculation in its own script can burden the controller. The rule is to let agents do the heavy work through ordinary commands, and keep the pipeline script itself thin.
Illustration: the busy script
A pipeline reads a huge report and processes it line by line in its own script. On the controller, this uses a lot of processor time and memory. Twenty such pipelines running at once make the whole Jenkins slow. Moving the processing into a command on an agent keeps the controller calm, and the same job finishes faster.
6Agents and How They Connect
Two ways for a worker to join the team.
An agent must connect to the controller before it can receive work. There are two main styles, and the choice affects networking, security and automation.
flowchart TD
subgraph SSHM["Controller starts the connection"]
C1["Controller"] -->|"opens secure shell connection"| A1["Agent machine"]
A1 --> R1["Agent program launched by controller"]
end
subgraph INB["Agent starts the connection"]
A2["Agent machine"] -->|"connects outward with secret"| C2["Controller"]
C2 --> R2["Agent stays connected and waits for work"]
end
| Style | Who connects | Best for | Watch out for |
|---|---|---|---|
| Secure shell agents | The controller connects to the agent | Stable Linux machines with known addresses | Controller needs network access and keys for every agent |
| Inbound agents | The agent connects to the controller | Machines behind firewalls, containers, cloud instances | Each agent holds a secret that must be protected |
| Inbound over web sockets | Agent connects using a standard web connection | Environments where only ordinary web traffic is allowed | Needs the controller to allow web socket traffic |
| Windows service agents | The agent runs as a service and connects inbound | Windows build machines | Service accounts and updates need care |
Which to choose at scale
For large, automatically created fleets, inbound agents are the natural fit. A newly created container or virtual machine starts, connects outward to the controller and announces itself. The controller does not need to know its address beforehand. For a small, fixed group of long-lived machines, secure shell is simple and well understood.
Whichever style you use, the connection between controller and agent must be authenticated and encrypted. Treat agent secrets and keys as credentials: never share one secret between many agents if you can avoid it, and remove them when an agent is retired.
7Executors: How Many Per Agent?
The number of slots decides how much competition happens on one machine.
Every executor on an agent can run one build. More executors mean more builds at once on the same machine, but also more competition for the processor, memory and disk. Too many executors make every build slower. Too few waste the machine.
| Type of work | Suggested executors per agent | Reason |
|---|---|---|
| Heavy compilation or large test suites | 1 to 2 | Each build wants many cores and much memory |
| Light scripts and quick checks | As many as the cores, or slightly more | Builds spend time waiting, not computing |
| Container-based agents | Usually 1 per container | Isolation is the whole point |
| Mixed workloads on one big machine | Number of cores divided by average cores per build | Match the slots to real demand |
The one-build-per-agent idea
Many large installations settle on one executor per agent, and scale by adding more small agents. This gives perfect isolation: a build cannot steal memory or disk from a neighbor, cannot leave files that disturb the next build unnoticed, and behaves the same every time. It suits containers and cloud virtual machines, which are cheap to start.
How to tune
- Watch processor and memory use during busy hours. If they sit near their limits, reduce executors.
- Watch idle time. If agents are mostly idle while builds queue for a different label, the labels or pools are wrong.
- Change one thing at a time, and compare average build duration before and after.
Illustration: more slots, slower builds
A team has eight-core machines with eight executors each. Builds that took ten minutes on a quiet machine take twenty-five when the machine is full, because they fight for memory. Cutting to three executors per machine makes each build take twelve minutes, so throughput actually rises even though fewer builds run at once.
8Labels and Matching
The addressing system that sends each build to a suitable worker.
A label is a short word attached to an agent. A pipeline asks for a label, and Jenkins picks an agent that has it. Labels can also be combined in label expressions using words such as and, or and not, for example “linux and docker” or “windows and not gpu”.
flowchart TD
ROOT["All agents"] --> OS["By operating system"]
ROOT --> SZ["By size"]
ROOT --> CAP["By capability"]
ROOT --> TR["By trust level"]
OS --> L["linux"]
OS --> W["windows"]
SZ --> S1["small"]
SZ --> S2["large"]
CAP --> D["docker"]
CAP --> G["gpu"]
TR --> T1["trusted"]
TR --> T2["untrusted"]
Good labeling rules
- Label by capability, not by project. A label such as
dockerstays useful for years. A label such aspayments-team-agentmultiplies pools and wastes capacity. - Keep dimensions independent. Operating system, size, capability and trust level combine freely through expressions.
- Keep the list short. Every new label splits the fleet into smaller, less flexible groups.
- Document each label, so people know what it promises.
Why too many labels hurt
Suppose you have 40 agents. If everything shares one label, any build can use any agent, so the fleet behaves like one big pool and queues stay short. If you split them into ten pools of four, one pool can be full while another sits idle. Fine-grained labels cost you the natural efficiency of sharing.
Prefer a few large pools with clear labels, and let containers provide the project-specific environment. The agent gives raw capacity; the container image gives the tools.
9How a Build Finds Its Agent
The queue, the scheduler and the moment of assignment.
flowchart TD
A["Build requests an agent with a label"] --> B["Item enters build queue"]
B --> C{"Free executor with matching label?"}
C -->|"Yes"| D["Scheduler picks a node"]
D --> E["Build starts on that agent"]
C -->|"No"| F{"Cloud can provision more?"}
F -->|"Yes"| G["Request new agent from cloud"]
G --> H["New agent connects"]
H --> D
F -->|"No"| I["Build waits in queue"]
I --> C
Request
A stage asks for an agent with a certain label. Jenkins creates a queue item and shows the reason it is waiting if no agent is free.
Match
The scheduler looks for agents whose labels satisfy the request and that have a free executor.
Choose
If several agents qualify, Jenkins chooses one. By default it prefers the agent where the job ran before, since its workspace may already hold the code.
Provision if needed
If no agent qualifies and a cloud is configured for that label, Jenkins asks it to create one.
Run
The build starts, the workspace is prepared and steps run on the agent.
Release
When the build ends, the executor is freed and the scheduler looks at the queue again.
Reading the queue
The queue page tells you why each item waits: “waiting for next available executor,” “all nodes of label X are offline,” or “waiting for an agent to be provisioned.” These reasons are the fastest diagnosis of a scaling problem. If many items wait for a label with no agents online, the fix is capacity or connectivity. If items wait even though agents look idle, suspect labels or throttling rules.
The preference for the agent where a job ran last is helpful when workspaces are reused, but can cause uneven load. If your agents are ephemeral or your builds start with a clean checkout, this preference brings little benefit.
10Static Agents: The Always-On Fleet
Permanent machines, their strengths and their weaknesses.
A static agent is a machine that is created once and stays online. It might be a physical server in an office, a virtual machine in a data center or a long-running cloud instance. Jenkins connects to it and reuses it for build after build.
Strengths
- Fast starts: no waiting for a machine to be created.
- Warm caches: dependencies and compiled files persist between builds.
- Special hardware is easy to attach.
- Simple to understand and to debug.
- Predictable cost for a steady load.
Weaknesses
- Idle machines still cost money.
- Leftover files and processes accumulate, causing strange failures.
- Builds can affect each other through shared state.
- Machines drift: patches and tools differ over time.
- Capacity is fixed, so peaks cause queues.
Keeping static agents healthy
- Clean workspaces at the end of builds, or on a schedule.
- Watch disk space. Jenkins can take an agent offline automatically when free space drops below a threshold, which is a useful safety net.
- Rebuild machines regularly from a known image, rather than patching them forever.
- Standardize tools so every agent with a given label behaves the same.
Illustration: the works-on-my-agent bug
A build passes on one agent and fails on another. The reason: the first agent has an old library version left over from a previous project. The build depended on it without anyone knowing. Rebuilding both agents from one image, and running builds in containers, removes the difference.
11Ephemeral Agents: Containers and Cloud Machines
Workers that exist only for the length of one build.
An ephemeral agent is created for a build and destroyed afterward. It might be a container started in a cluster, or a virtual machine created by a cloud provider on request. Nothing survives from one build to the next, which makes every build start from a clean, known state.
flowchart LR
Q["Build waits in queue"] --> R["Cloud plugin requests an agent"]
R --> S["Container or VM starts from an image"]
S --> C["Agent connects to controller"]
C --> B["Build runs on clean environment"]
B --> D["Build ends"]
D --> X["Agent destroyed"]
X --> F["Resources returned, billing stops"]
| Question | Static agent | Ephemeral agent |
|---|---|---|
| Startup time | None, already running | Seconds to minutes to create |
| Clean environment | Only if you clean it | Always |
| Cost when idle | Yes | None |
| Caches | Warm and local | Cold unless shared |
| Isolation between builds | Weak | Strong |
| Capacity limit | Fixed | Limited by cluster or account quotas |
| Best for | Special hardware, steady load | Variable load, clean and reproducible builds |
Why ephemeral agents dominate at scale
They turn cost into a function of usage: no builds, no agents, no bill. They make every build reproducible, because the environment is defined by an image, not by the history of a machine. And they make security simpler, because a compromised build cannot leave anything behind for the next one.
Many teams use both. A small pool of static agents handles special hardware and provides warm capacity for the first few builds, while an elastic pool absorbs the peaks.
12Autoscaling: Growing and Shrinking With the Queue
The feedback loop that keeps capacity close to demand.
Autoscaling means adding agents when work is waiting and removing them when they are idle. Jenkins does this through cloud plugins that watch the queue, ask a provider for more agents, and later shut down the ones that are no longer needed.
flowchart LR
A["Queue grows"] --> B["Cloud plugin sees waiting builds"]
B --> C["Provision new agents up to a limit"]
C --> D["Agents connect and take work"]
D --> E["Queue shrinks"]
E --> F["Agents become idle"]
F --> G{"Idle longer than the retention time?"}
G -->|"Yes"| H["Terminate agent"]
G -->|"No"| F
H --> I["Capacity matches demand"]
I --> A
The key settings
Maximum agents
The most agents the cloud may create at once. Protects you from runaway cost and from exhausting the cluster or account quota.
Minimum or warm agents
A few agents kept ready, so the first builds of the morning do not wait for creation.
Idle retention
How long an idle agent stays before removal. Short saves money; longer avoids repeated startup costs.
Provisioning rate
How many agents may be requested at once. Too slow leaves queues; too fast can overload the provider or the controller.
Common surprises
- Provisioning delay. Creating a virtual machine can take a minute or several. Containers start faster, but still need to pull an image.
- Thundering herd. At nine in the morning, hundreds of builds start at once. Autoscaling reacts after the queue grows, so the first minutes are slow. Warm capacity or scheduled scaling before peak hours smooths this.
- Quota walls. Providers limit how many machines you may create. Hitting the wall makes builds wait even though the cloud plugin is trying.
- Flapping. If idle retention is very short and builds arrive in bursts, agents are created and destroyed constantly, wasting time.
Illustration: the morning rush
A team’s builds jump from 5 to 120 within ten minutes every weekday at nine. New agents take about three minutes to become ready, so developers wait longer at nine than at any other time. The team keeps twenty warm agents from 8:45 by schedule, and the morning queue drops to under a minute.
13Kubernetes Agents and Pod Templates
Using a container cluster as an agent factory.
Kubernetes is a system that runs and manages containers across many machines. The Jenkins Kubernetes plugin lets Jenkins ask a cluster for a pod, which is one or more containers running together, for each build. The pod becomes the agent, runs the build steps, and is deleted when the build ends.
The pod template
A pod template describes what the agent looks like: which container images it contains, how much processor and memory each may use, which storage to attach and which labels it carries. Pipelines can either refer to a template by label or define one inline.
Request
The pipeline asks for an agent. Jenkins sends the pod description to the cluster.
Scheduling
The cluster finds a machine with enough free resources, or adds a machine if it can autoscale.
Image pull
The machine downloads the container images. Large images take longer, especially on the first pull.
Connect
A small agent container connects back to the controller and joins the build.
Run and delete
Steps run in the requested containers. When done, the pod is deleted and its resources are freed.
Benefits and cautions
| Benefit | Caution |
|---|---|
| Each build gets its own clean environment | Cold caches, so downloads repeat unless shared |
| Different steps can use different tool images in one pod | Image size and pull time affect start-up |
| Resource requests and limits control fairness | Wrong limits cause slow builds or killed containers |
| The cluster scales machines automatically | Node start-up adds delay at peak times |
| Standard, declarative definition of agents | Another platform to run and secure |
Setting resource limits too low can make the cluster stop a container when it uses too much memory, which looks like a mysterious build crash. Set requests to typical usage and limits to a safe ceiling, and watch for such stops in the cluster’s events.
14Moving Data: Workspaces, Stash and Artifacts
When work spreads across machines, files need a way to travel.
In a distributed pipeline, one stage may run on one agent and the next on a different one. Files created in the first workspace do not follow automatically. Jenkins offers several ways to move them, each suited to a different size and purpose.
flowchart TD
A["Agent 1: build stage"] --> S["Stash small files through controller"]
A --> AR["Archive artifacts"]
A --> EX["Upload to external artifact repository"]
S --> B["Agent 2: test stage unstashes"]
AR --> H["Kept with the build record"]
EX --> D["Agent 3: deploy stage downloads"]
EX --> R["Long-term storage and sharing"]
| Method | What it is | Good for | Limits |
|---|---|---|---|
| Stash and unstash | Temporary storage of files during one build, passed between stages | Small sets of files, such as compiled output for the test stage | Travels through the controller; large stashes slow it and are discouraged |
| Archive artifacts | Saving chosen files with the build record | Reports and small packages people may download | Stored on the controller by default; large volumes fill its disk |
| External artifact repository | A dedicated server for packages and images | Large files, releases, images and long-term storage | An extra system to run |
| Artifact manager plugins | Store archives and stashes in cloud object storage instead of the controller | Large installations that still want the archive feature | Needs plugin setup and permissions |
The controller is not a file server
Stash and archive normally send data through the controller. A few large builds doing this at the same time can saturate its network and disk. At scale, send big files straight from the agent to an artifact repository or object storage, and pass only a reference between stages.
Workspace hygiene
- Clean workspaces when builds finish, or use ephemeral agents so this happens by design.
- Use unique workspace paths when several builds of the same job can run together, so they do not overwrite each other.
- Never leave secrets in a workspace that a later build could read.
Illustration: the swollen controller
A team archives a 1.5 gigabyte package with every build, and runs 200 builds a day. In a month the controller’s disk holds nearly nine terabytes of archives, and its network is busy uploading them. Switching to an external artifact repository and archiving only reports brings the controller back to a few gigabytes.
15Caching and Faster Checkouts
Most of a build’s time is often spent fetching things, not building them.
Before a build can compile anything, it must download the source code and the libraries it depends on. On a fresh ephemeral agent, this happens every time. Across hundreds of builds, it adds up to hours of waiting and heavy network traffic. Caching attacks that waste.
Shallow and partial checkouts
Fetch only the latest history, or only the folders you need, instead of the whole repository history. Large repositories become much quicker.
Reference repositories
Keep a shared local copy of the repository on each agent or a shared volume, so a checkout only fetches the difference.
Package proxies
Run an internal proxy that stores every library you download once. Agents fetch from the nearby proxy at high speed and the outside world is contacted rarely.
Image caches
Keep commonly used container images on cluster nodes or in a nearby registry mirror, so agents start faster.
Build caches
Reuse compiled outputs from earlier builds when the inputs have not changed, using tool-specific remote caches.
Persistent cache volumes
Attach a shared or persistent storage volume for dependency folders so ephemeral agents start warm.
The trade-off with clean builds
Caches make builds faster, but stale caches can hide problems: a build passes because of a leftover file that should not exist. Use caches for downloads and compiled dependencies, keyed by the exact versions, and keep the final build steps clean. Regularly run a full clean build, for example nightly, to confirm that nothing depends on hidden leftovers.
Illustration: the ten-minute download
A build spends ten minutes downloading libraries and two minutes compiling. After adding a package proxy inside the same network, the downloads take forty seconds. With 300 builds a day, that saves about forty-six hours of agent time daily, without touching a line of code.
16Splitting Work Across Agents
Making one slow build faster by running its parts side by side.
So far we have used many agents to run many builds at once. Distribution can also make a single build faster, by splitting its work into pieces that run on different agents at the same time. Jenkins pipelines do this with the parallel step.
flowchart TD
B["Build stage finishes"] --> SP["Split test suite into four groups"]
SP --> T1["Agent A: group 1"]
SP --> T2["Agent B: group 2"]
SP --> T3["Agent C: group 3"]
SP --> T4["Agent D: group 4"]
T1 --> J["Join: collect all results"]
T2 --> J
T3 --> J
T4 --> J
J --> G["Gate and report"]
Ways to split
- By kind of test: unit, integration and browser tests in separate branches.
- By operating system or version: the same tests on several platforms at once, often called a matrix.
- By test groups: divide a large suite into equal parts by past duration. A plugin for parallel test execution can compute balanced groups from previous run times.
Costs to keep in mind
| Cost | Explanation |
|---|---|
| Setup repeated | Each branch needs its own agent, checkout and dependency download, which can outweigh the gain for short tasks |
| Uneven groups | The stage lasts as long as the slowest branch, so imbalance wastes time |
| More capacity used at once | Four branches take four executors; a busy fleet may make them wait |
| Result merging | Reports from several agents must be collected and combined |
Split only when the work is long enough to repay the extra setup, usually more than a few minutes. Use the fail-fast option so that when one branch fails, its siblings stop and release their agents.
17Capacity Planning With Simple Arithmetic
How many executors do you really need?
A useful rule from queueing theory, known as Little’s law, says that the average number of things in a system equals the rate at which they arrive multiplied by the average time each one spends there. For builds, this gives a simple estimate of how many executors are busy on average.
flowchart LR
A["Arrival rate: builds per hour"] --> M["Multiply"]
B["Average build duration in hours"] --> M
M --> C["Average executors busy"]
C --> D["Multiply by peak factor of 2 to 3"]
D --> E["Executors needed at peak"]
E --> F["Compare with fleet size and ceiling"]
Worked example (illustrative numbers)
A team runs about 600 builds during a ten-hour working day, which is 60 builds per hour. The average build takes 12 minutes, or 0.2 hours. Multiplying gives 60 times 0.2, which equals 12 executors busy on average. Builds arrive in bursts, so a peak factor of 2.5 suggests about 30 executors at the busiest moments. With four executors per agent, the team needs around eight agents at peak, and far fewer overnight.
Beyond the average
The average tells you the minimum. Queues appear when arrivals bunch up. To keep waiting times low, plan for the peak, not the average, and track the real queue wait time. A useful goal is that most builds start within a minute of being queued.
| Signal | Meaning | Action |
|---|---|---|
| Queue wait consistently low, executors mostly idle | Over-provisioned | Reduce warm capacity or shrink pools |
| Queue wait spikes at known hours | Peak not covered | Schedule warm capacity, raise the ceiling |
| Queue wait high all day | Under-provisioned | Add capacity, split slow builds, remove waste |
| Builds wait though agents are idle | Labels or limits mismatch | Review labels, throttle rules and node modes |
Before adding machines, shorten the builds. Cutting the average duration from twelve to eight minutes reduces the average busy executors by a third, without any extra cost. Capacity is the product of arrivals and duration, and duration is often the cheaper factor to change.
18Controller Performance and Tuning
Keeping the manager fast while the workers multiply.
As the fleet grows, the controller’s job grows too. It tracks more agents, more running builds, more logs streaming in, more history and more web requests. Its limits are memory, disk speed, network and the number of plugins and jobs it must manage.
Java heap size
Jenkins runs on Java. Give it enough memory for the number of jobs and builds, but not so much that pauses for cleanup become long. Monitor memory use and cleanup pauses.
Fast storage for the home folder
The controller writes constantly: logs, build records and pipeline state. Slow disks make everything slow. Use fast local or high-performance network storage.
Discard old builds
Keep only as many builds and artifacts as people use. Every stored build costs disk and load time.
Durability setting
Pipelines save their progress to disk so they can survive a restart. A performance-optimized setting writes less often, trading a little resilience for much less disk activity. Use it for pipelines that can simply be rerun.
Log volume
Very chatty builds stream huge logs through the controller. Reduce noisy output, and send bulk logs to external systems.
Fewer, well-chosen plugins
Every plugin adds load, memory and risk. Remove those you do not use, and update carefully.
Signs the controller is struggling
- The web interface is slow to load, even when few people use it.
- Builds start late even though agents are idle.
- Agents disconnect and reconnect at random.
- Long pauses in the controller’s memory cleanup.
- Disk usage or disk wait times climb steadily.
Illustration: the ten thousand jobs
A team’s controller manages twelve thousand jobs, including thousands of stale branch jobs and old builds. Startup takes twenty minutes and the interface is sluggish. After cleaning old branches, discarding builds beyond the last twenty and removing unused plugins, startup drops to four minutes, and page loads feel instant again.
19Scaling Out: More Than One Controller
When one manager is no longer enough.
A single controller cannot grow forever. Its memory, its disk and the number of plugins and jobs it can handle are finite, and it is a single point of failure: if it stops, nobody builds. Very large organizations therefore run several controllers, each serving a group of teams.
flowchart TD
ORG["Organization"] --> C1["Controller: payments teams"]
ORG --> C2["Controller: mobile teams"]
ORG --> C3["Controller: data teams"]
C1 --> A1["Agent pool 1"]
C2 --> A2["Agent pool 2"]
C3 --> A3["Agent pool 3"]
C1 -.-> SH["Shared services: artifact repository, package proxy, secret manager, monitoring"]
C2 -.-> SH
C3 -.-> SH
| One big controller | Several smaller controllers |
|---|---|
| One place to manage and upgrade | More installations to keep consistent |
| One failure affects everyone | Failures affect only one group |
| Plugin choices shared by all | Teams can choose plugins and versions that suit them |
| Growth eventually hits hard limits | Growth by adding controllers |
| Simple permissions model | Cleaner separation of duties and data |
Making many controllers manageable
- Define everything as code. Use configuration as code so each controller can be recreated from files, and made identical.
- Standardize a base image with a tested set of plugins, and update it centrally.
- Share what can be shared: artifact storage, package proxies, secret managers and monitoring.
- Give teams ownership, within guardrails set by a platform team.
Consider another controller when one is regularly near its memory or disk limits despite cleanup, when an outage would stop too many teams, or when groups need different plugins, policies or security boundaries.
20Reliability: When Agents Fail
In a large fleet, some machine is always about to break.
With hundreds of agents, failures are not exceptions but routine: a machine runs out of disk, a network connection drops, a cloud provider reclaims a cheap instance, a container is killed for using too much memory. A reliable setup expects these events and limits the harm.
flowchart TD
A["Agent lost during build"] --> B{"Cause"}
B -->|"Machine or network problem"| C["Build marked failed or aborted"]
B -->|"Cloud reclaimed instance"| C
C --> D{"Retry allowed for agent loss?"}
D -->|"Yes"| E["Retry on a new agent"]
D -->|"No"| F["Report and alert"]
E --> G["Build completes"]
F --> H["Cleanup and investigate"]
Defenses
Node monitors
Jenkins watches free disk space, clock differences and response time, and can take a struggling agent offline before it ruins builds.
Retry on agent loss
Newer versions of the retry step can retry specifically when the agent was lost. Use it for builds that are safe to repeat.
Timeouts
Deadlines on stages and waits stop builds that hang on a half-dead agent.
Self-healing fleets
Automated groups replace failed machines with fresh ones, without human action.
Quarantine flaky agents
When failures cluster on one agent, take it offline and investigate, instead of blaming the code.
Repeatable steps
Design steps so that running them twice is safe, especially deployment steps, which should verify state first.
Retrying blindly can hide real problems and repeat harmful actions. Retry only when the loss was clearly the machine’s fault, and never retry a step that is not safe to repeat, such as one that charges a customer.
The controller as a single point of failure
The open-source controller is one active instance. Protect it with frequent backups of its home folder, configuration as code that can rebuild it quickly, and a tested recovery plan. Practice restoring it, because an untested backup is only a hope.
21Cost Control
Scale should not mean an unpleasant bill.
Distributed builds turn engineering time into cloud spending. The goal is not the lowest bill but the best balance between developer waiting time and money. An hour of a developer’s time usually costs more than an hour of a build machine, so cutting wait times is often worth paying for.
| Lever | How it saves | Trade-off |
|---|---|---|
| Ephemeral agents | No idle cost | Startup time and cold caches |
| Spot or preemptible machines | Much cheaper machines that the provider can reclaim | Builds may be interrupted, so retries are needed |
| Right-sized agents | Match machine size to work | Needs measurement and several sizes |
| Short idle retention | Stop paying quickly | More creation and destruction |
| Scheduled scaling | Fewer machines overnight and on weekends | Must adapt to different time zones |
| Faster builds | Fewer machine minutes per build | Engineering effort |
| Timeouts | Cap the cost of hung builds | Need sensible limits |
| Skip needless builds | Do not build documentation-only changes | Rules must be accurate |
Make cost visible
Tag every agent with the team or project it serves, and report the cost of builds per team. When teams see what their builds cost, they choose faster tests and smarter triggers without being told. Without visibility, costs grow quietly until a large bill forces a crisis.
Illustration: the weekend bill
A team keeps thirty warm agents running around the clock because “the first build should be fast.” Nobody builds on weekends, so 60 hours of thirty machines are wasted every week. Scheduling the warm pool for weekday working hours cuts that part of the bill by more than two thirds, and nobody notices any slowdown.
22Security in a Distributed Setup
More machines mean more places where things can go wrong.
Every agent runs code, some of it written by people you do not fully control. Every connection between agent and controller is a path that must be protected. Security in a distributed Jenkins rests on a few clear principles.
Keep builds off the controller
Set the controller’s own executors to zero. Untrusted build code must never run where the secrets live.
Restrict what agents can ask of the controller
Jenkins includes controls that limit what an agent may do to the controller. Keep them enabled, so a compromised agent cannot read files or run commands on the manager.
Separate trust levels
Run builds of untrusted code, such as pull requests from outsiders, on separate agents from builds that deploy or hold secrets.
Prefer ephemeral, disposable agents
Nothing survives to be stolen or poisoned by an earlier build.
Give credentials only to trusted builds
Bind secrets only in builds of protected branches, and only to the steps that need them.
Secure the network
Place agents in separate network segments, allow only the connections they need, and encrypt every link.
Agent secrets and keys
Give each agent its own secret where possible, and rotate or revoke it when the agent is retired.
Trusted base images
Build agent images from reviewed sources, scan them for vulnerabilities and rebuild regularly.
Container privileges
Avoid giving builds full control of the host. Mounting the host’s container socket lets a build take over the machine.
Logs and access reviews
Record who changed agent settings, and review who may create or configure agents.
Assume that any code that runs on an agent can read everything that agent can read. Then make sure that what an agent can read is only what its builds truly need.
23Monitoring, Logging and Metrics
You cannot scale what you cannot see.
A distributed system fails in many small ways. Good monitoring turns vague complaints, such as “Jenkins feels slow,” into specific findings, such as “the Windows pool has been full since ten.” Watch the system at three layers: the queue, the agents and the controller.
| Layer | Metric | Warning sign |
|---|---|---|
| Queue | Queue length and wait time per label | Wait time above a minute for common labels |
| Queue | Reasons items are waiting | Many waiting for offline or missing labels |
| Agents | Executor utilization per pool | Constantly near 100 percent, or constantly near zero |
| Agents | Agent start-up time | Provisioning taking minutes at peak |
| Agents | Disk, memory, processor on agents | Free disk shrinking; memory near limits |
| Agents | Agent disconnects and losses | Rising numbers or clustering on certain machines |
| Controller | Memory use and cleanup pauses | Long or frequent pauses |
| Controller | Disk usage and write delay | Steady growth; slow writes |
| Controller | Web response time | Slow pages under normal load |
| Business | Build duration percentiles | Slow drift upward |
| Business | Cost per build or per team | Sudden jumps |
Tools
Monitoring plugins can expose Jenkins numbers to systems such as Prometheus, and dashboards, meaning wall screens of graphs, make the health of the fleet visible to everyone. The cluster or cloud provider adds its own metrics for machines and containers. Combine them so that a queue spike on the Jenkins side can be matched to a provisioning delay on the cloud side.
Alerts that matter
- Queue wait above target for more than a few minutes.
- Agents offline beyond a threshold, or a pool with zero agents.
- Controller disk above a safe level.
- Provisioning failures from the cloud, which often mean a quota was reached.
- Sharp increase in aborted or lost builds.
Illustration: the invisible bottleneck
Developers complain that builds are slow. The dashboard shows agents at 30 percent utilization, but the queue for one label is long. A single label carrying only three agents is the bottleneck, while ninety agents with other labels sit idle. Adding that label to more agents fixes the problem in ten minutes.
24Configuration as Code and Image Baking
Making the fleet reproducible instead of hand-crafted.
At scale, nobody can click through the web interface to set up every agent, cloud and setting. Two practices make large installations manageable: describing Jenkins itself in files, and building agents from prepared images.
Jenkins Configuration as Code
The Configuration as Code plugin lets you describe the controller’s settings, clouds, agent templates, security rules and credentials sources in text files stored in version control. Applying the files recreates the same configuration. A new controller can be built in minutes, changes are reviewed like any other code, and a mistake can be rolled back.
Image baking
An image is a saved snapshot of a machine or container with all its tools installed. Baking means preparing that image ahead of time, so that new agents start already complete, instead of installing tools at start-up. Baked images make agents start faster and behave identically every time.
flowchart LR
R["Repository of image definitions"] --> B["Automated image build"]
B --> T["Test the image"]
T --> S["Scan for vulnerabilities"]
S --> P["Publish to registry or image store"]
P --> C["Cloud templates reference new image version"]
C --> A["New agents start from it"]
A --> M["Old images retired on a schedule"]
| Practice | Benefit | Cost |
|---|---|---|
| Configuration as code | Reproducible, reviewable, recoverable controllers | Learning the format; discipline to avoid manual changes |
| Baked images | Fast, identical, scannable agents | An image pipeline to maintain |
| Install tools at start-up | Simple to begin with | Slow starts and differences between agents |
| Versioned images | Easy rollback and clear audit | More versions to track and retire |
Keep images small and layered: a common base with the operating system and the agent program, and thin layers for language tools. Small images pull faster, and a fix to the base helps every agent type.
25Useful Design Patterns
Proven ways of arranging a distributed Jenkins.
Pattern 1: Thin controller, fat agents
The controller runs no builds and little scripting logic. Agents do all the real work. The manager stays fast however large the fleet grows.
Pattern 2: One executor per ephemeral agent
Each build gets its own clean container or machine. Isolation is perfect, results are reproducible and scaling is a matter of starting more agents.
Pattern 3: Warm core plus elastic burst
A small pool of always-on agents covers baseline demand and special hardware, while an elastic pool absorbs peaks. Developers get fast starts without paying for idle machines around the clock.
Pattern 4: Capability labels, not project labels
Label agents by what they can do. Let container images provide project-specific tools. The fleet stays one large, efficient pool.
Pattern 5: Shared caches and proxies
Put a package proxy, a registry mirror and a shared build cache near the agents. Ephemeral agents then start warm without keeping state themselves.
Pattern 6: Trust tiers
Run untrusted builds on isolated agents with no credentials, and trusted builds on separate agents that may deploy. A compromise of the first tier reaches nothing valuable.
Pattern 7: Controller per group with shared platform services
Teams get their own controllers for autonomy and containment, all built from the same code, sharing storage, proxies, secrets and monitoring.
Pattern 8: Split long tests, join the results
Divide slow test suites into balanced groups that run on parallel agents, and merge the reports into one verdict.
26Anti-patterns to Avoid
Habits that look fine at first and hurt later.
What people do
They let builds run on the controller.
Why it hurts
A heavy or hostile build can slow or crash the manager, and untrusted code sits next to every secret.
Better approach
Set the controller’s executors to zero and run everything on agents.
What people do
They create a label and a small pool for every project.
Why it hurts
Some pools are full while others sit idle, so queues form despite spare capacity.
Better approach
Label by capability, share large pools, and use container images for project needs.
What people do
They send large stashes and archives through the controller.
Why it hurts
The controller’s network and disk are overwhelmed, and everything slows.
Better approach
Use an artifact repository or object storage, and archive only small reports.
What people do
They keep hand-built, long-lived agents that nobody dares to rebuild.
Why it hurts
Machines drift apart, builds pass on one and fail on another, and nobody can reproduce the setup.
Better approach
Build agents from versioned images and replace them regularly.
What people do
They add machines whenever the queue grows, without measuring.
Why it hurts
Costs rise while the real causes, slow builds, missing caches or mismatched labels, remain.
Better approach
Measure arrivals, duration and wait time, shorten builds first and then add capacity.
What people do
They run untrusted pull request builds on the same agents that deploy.
Why it hurts
An outsider’s code can reach credentials and production paths.
Better approach
Separate trust tiers, and give untrusted builds no secrets.
What people do
They set no ceiling on autoscaling and no deadlines on builds.
Why it hurts
A bug or attack can create hundreds of machines and a very large bill.
Better approach
Set a maximum number of agents, add timeouts and alert on spending.
27Best Practices and Common Mistakes
A checklist to apply to any Jenkins setup today.
Do this
- Keep the controller free of builds.
- Use ephemeral agents for most work.
- Label by capability and keep the list short.
- Measure queue wait, utilization and build duration.
- Plan capacity from arrival rate and duration.
- Send big files to artifact storage, not through the controller.
- Use proxies and caches for downloads.
- Describe everything as code and bake images.
- Set ceilings, timeouts and cost alerts.
- Separate trusted and untrusted builds.
Avoid this
- Running builds or heavy scripts on the controller.
- One pool per project.
- Too many executors on one machine.
- Hand-built agents that drift.
- Unlimited autoscaling.
- Keeping every build and artifact forever.
- Blind retries of unsafe steps.
- Guessing capacity instead of measuring.
- Letting untrusted code reach secrets.
Mistakes people make when they are new
Scaling machines before fixing the queue’s cause. Look at why builds wait before buying more capacity.
Ignoring start-up time. Ephemeral agents are clean, but a three-minute start-up on every build annoys people. Use warm pools and small images.
Forgetting the cloud quota. Autoscaling stops at the provider’s limit, and builds wait quietly. Watch quotas and request increases before you need them.
Underestimating log volume. Chatty builds add up. Reduce noise, or send logs elsewhere.
Skipping the recovery drill. A controller backup that has never been restored is not yet a backup.
28Advantages, Disadvantages and Trade-offs
An honest look at what distribution does well and where it strains.
Advantages
- Many builds run at once, so queues stay short.
- Different operating systems and hardware are supported.
- A bad build harms only its own agent.
- Capacity grows and shrinks with demand.
- Single long builds can be split and finished sooner.
- Clean, reproducible environments through containers and images.
Disadvantages
- More parts to run, monitor and secure.
- Network and start-up delays add overhead.
- Data must move between machines.
- Caches are harder to keep warm.
- Costs can grow without controls.
- The controller remains a single point of coordination.
Trade-offs to weigh
| Choice | Benefit | Cost |
|---|---|---|
| Static agents | Instant start, warm caches | Idle cost and drift |
| Ephemeral agents | Clean and cost-proportional | Start-up time and cold caches |
| Many executors per agent | Efficient use of big machines | Builds interfere with each other |
| One executor per agent | Isolation and predictability | More agents and overhead |
| Warm pool | Fast starts at busy times | Paying for idle capacity |
| Spot machines | Big savings | Interruptions and retries |
| One controller | Simplicity | Limits and larger blast radius |
| Many controllers | Isolation and growth | More to standardize and upgrade |
There is no universally right answer. The right design depends on your load pattern, your budget, your security needs and your team’s skills. Choose on purpose, measure the results, and write down why.
29Real-World and Industry Examples
How the ideas look in the kinds of organizations that run Jenkins at scale.
A large e-commerce company
Retail engineering organizations with thousands of developers commonly run many controllers, each serving a set of teams, with elastic container agents that scale up before the working day and down at night. Shared artifact storage and package proxies keep builds quick.
A bank with strict security
Regulated firms separate build networks by trust level, run builds on disposable machines, hold secrets in a central manager and record every change to agent configuration. Scaling is careful and audited.
A mobile app studio
Building for phones needs special machines. The studio keeps a modest static pool of specialized hardware, and uses elastic Linux agents for everything else. Labels route each job to the right kind.
A game developer
Game builds are huge and use graphics hardware. The team uses large agents, cache build outputs remotely, and splits asset processing across many machines to bring hours-long builds down to minutes.
An open-source foundation
Projects that build outside contributions use isolated, short-lived agents with no credentials for pull requests, and separate trusted agents for releases. Volunteer-donated machines join through inbound connections.
A growing startup
A team of twenty starts with two static agents, moves to container agents on a managed cluster as builds multiply, adds a package proxy after downloads dominate build time, and sets a spending alert. Each step responds to a measured problem, not a guess.
30A Complete Story: Growing From One Machine to a Fleet
Follow one imaginary team through three stages of growth.
One machine
A team of six runs Jenkins on one server, which also builds. It works until builds overlap and the web page starts to crawl.
Controller and two agents
They set the controller’s executors to zero, add two static agents with clear labels and watch the queue. Waiting drops from twenty minutes to two.
The team grows to sixty
Builds reach 600 a day. Using simple arithmetic, they estimate twelve executors busy on average and about thirty at peak. Static agents cannot cover the peak economically.
Containers on a cluster
They move to ephemeral container agents, one per build, from baked images. A cluster adds machines as needed and removes them at night.
The morning rush
At nine, builds spike and starts take three minutes. They keep twenty warm agents from 8:45 and add a package proxy. The queue at nine falls under a minute.
Fixing the real bottleneck
Tests take forty minutes. They split them across four agents with balanced groups. Duration drops to twelve minutes, and average busy executors fall too.
Guardrails
They set a ceiling on agents, timeouts on stages, alerts on queue wait and cost, trust tiers for pull requests, and configuration as code for the controller.
A bad night
A cloud instance is reclaimed mid-build. The agent-loss retry starts the build again on a fresh agent. Nobody is woken up, and the morning report shows one retry.
At each step the team measured first and changed second. They never needed a heroic redesign. Distribution grew with their needs, one understood improvement at a time.
31Troubleshooting Guide
Symptoms, likely causes and calm fixes.
| Symptom | Likely cause | What to do |
|---|---|---|
| Builds wait though agents look idle | Label mismatch or throttling rules | Read the queue reason; compare labels and node usage modes |
| Queue long at nine every morning | Autoscaling reacts after the rush begins | Schedule warm capacity ahead of peak |
| Agents will not appear | Quota reached, network blocked or image failing | Check cloud events, quotas and agent logs |
| Agents connect then disconnect | Network drops, resource limits or wrong secret | Check connectivity, memory limits and agent logs |
| Random build crashes in containers | Memory limit exceeded and container stopped | Raise limits; check cluster events |
| Controller slow, agents idle | Heap, disk or log volume problem | Check memory, disk speed and log volume |
| Controller disk filling | Archived artifacts and old builds | Use artifact storage; discard old builds |
| Builds pass on one agent, fail on another | Drifted static agents | Rebuild from images; use containers |
| Builds slow only on some agents | Too many executors or noisy neighbors | Reduce executors; monitor resource use |
| Bill jumped suddenly | No ceiling, hung builds or idle warm pools | Set ceilings, timeouts and schedules; review tags |
A calm debugging routine
- Open the queue and read the reason each item waits.
- Check the pool for the label: how many agents, how many free, how many offline.
- Check the provider for provisioning errors and quotas.
- Check the controller for memory, disk and response time.
- Change one thing at a time and compare queue wait before and after.
32Plain-English Glossary
Every important word in this guide in one place.
| Word | Simple meaning |
|---|---|
| Jenkins | A free automation server that builds, tests and delivers software. |
| Pipeline | A written recipe of the steps Jenkins follows. |
| Controller | The main Jenkins server that schedules work and keeps history. |
| Agent | A worker machine or container that runs build steps. |
| Executor | A slot on an agent that runs one build at a time. |
| Label | A tag on an agent used to route work. |
| Build queue | The waiting list of builds without a free executor. |
| Workspace | The working folder of a build on an agent. |
| Static agent | A permanent, always-on worker. |
| Ephemeral agent | A worker that exists for one build only. |
| Cloud plugin | A plugin that creates and removes agents automatically. |
| Autoscaling | Adding and removing agents to match demand. |
| Kubernetes | A system that runs containers across many machines. |
| Pod | One or more containers running together as a unit. |
| Stash | Temporary storage of files passed between stages. |
| Artifact | A file saved from a build for later use. |
| Little’s law | Average items in a system equal arrival rate times average time spent. |
| Configuration as Code | Describing Jenkins settings in files. |
| Image baking | Preparing an agent image in advance with all tools installed. |
| Spot instance | A cheap cloud machine that the provider may reclaim. |
33Frequently Asked Questions
Short answers to the questions that come up most often.
34Summary and Key Takeaways
The whole story in a few lines.
Distributed builds let Jenkins scale by separating thinking from doing. A small, well-protected controller schedules and records. Many agents, ideally short-lived containers or cloud machines, do the work. Labels route jobs to suitable machines, autoscaling matches capacity to demand, caches and artifact stores keep data moving quickly, and parallel splitting shortens slow builds. Simple arithmetic guides capacity, monitoring reveals bottlenecks, configuration as code and baked images keep everything reproducible, and careful separation of trust levels keeps it safe. The best setups grow one measured improvement at a time.
Key Takeaways
- Thin controller, many agents. Set the controller’s executors to zero, keep pipeline scripts light and let agents do all the heavy work.
- Prefer ephemeral agents. One clean container or machine per build gives isolation, reproducibility and cost that follows demand, with a small warm pool for fast starts.
- Label by capability. Few, clear labels keep the fleet one efficient shared pool, and container images provide project-specific tools.
- Plan with arithmetic and measure the queue. Executors busy equals builds per hour times duration in hours, then allow for peaks and watch real wait times.
- Shorten builds before adding machines. Cache downloads, use proxies, use shallow checkouts and split long tests across agents.
- Protect the controller and the data path. Send big files to artifact storage, discard old builds, tune memory and disk, and split into several controllers when limits are reached.
- Add guardrails. Set ceilings, timeouts, cost alerts and retries for lost agents, describe everything as code, and separate trusted from untrusted builds.