Jenkins Pipeline Stages and Parallel Execution: Stop Wasting CI/CD Minutes
Master Jenkins pipeline stages and parallel execution with production patterns.
20+ years shipping production infrastructure and CI/CD at scale. Notes here come from systems that actually shipped.
Use parallel execution when you have independent tasks like running tests on different platforms or building separate microservices. Always limit parallelism with failFast true and monitor executor usage to avoid exhausting your Jenkins controller.
Think of a car assembly line. Stages are the stations: weld body, paint, install engine. Normally each car goes through one station at a time. Parallel execution is like having two identical lines side by side — you can weld two bodies simultaneously, but you need twice the workers (executors). If you only have one paint booth, painting becomes a bottleneck.
Your CI pipeline takes 45 minutes. Developers are twiddling thumbs waiting for green builds. You've heard 'parallel execution' is the answer. But slap parallel on a few stages and suddenly your Jenkins master crashes at 3 AM because you ran out of executors. I've seen it. The fix isn't just 'add more agents' — it's understanding how stages and parallelism actually interact under load. This article gives you the patterns I've used to cut build times by 70% without burning down the Jenkins controller. You'll learn when to parallelize, when not to, and how to fail fast when things go sideways.
Why Stages Exist: The Problem Before Pipelines
Before Declarative Pipeline, we had freestyle jobs with a single build step. If you wanted to compile, test, and deploy, you hacked it together with shell scripts. Failures were opaque — you'd see 'Build failed' but not where. Stages gave us visibility: each stage shows green/red in the UI. But the real win is control — you can define post-actions per stage, skip stages conditionally, and parallelize within a stage. Without stages, you can't parallelize meaningfully because there's no structure to split work.
stage blocks even for single-step pipelines. The UI visualization alone is worth it. Plus, you can add when conditions later without restructuring.Parallel Execution: The Obvious Win and the Hidden Trap
Parallel execution runs multiple stages concurrently. The obvious win: if you have three independent test suites, run them in parallel and cut test time from 30 minutes to 10. The hidden trap: each parallel branch typically requires an executor. If you have 4 executors and launch 10 parallel branches, 6 will queue — or worse, the controller's thread pool overflows. The fix: limit parallelism explicitly. In Declarative Pipeline, use parallel with a map. In Scripted Pipeline, use parallel with a list of closures. Always set failFast true so one failure stops all branches — otherwise you waste resources on doomed branches.
agent none at the pipeline level, the outer pipeline grabs an executor and sits idle while parallel branches run. That executor is wasted. Always set agent none when using parallel with explicit per-stage agents.Fail Fast: Don't Let Dead Branches Waste Resources
By default, if one parallel branch fails, the others keep running. That's stupid. You're burning CI minutes on work that's already doomed. Add failFast true to the parallel block. In Declarative, it's a property of the parallel directive. In Scripted, pass failFast: true to the parallel call. But be careful: failFast kills all branches immediately. If you have cleanup logic in post, it still runs per branch. I've seen teams lose artifacts because a failing test killed the build before archiving. Solution: move artifact archiving to a separate stage after parallel.
failFast true aborts them. But post actions still run per branch. That's a common gotcha.Locking Resources: When Parallelism Bites You
Parallel execution is great until two branches try to write to the same file or database. Classic rookie mistake: parallel integration tests that both create the same test data. You get race conditions and flaky tests. The fix: use the lock step to serialize access to shared resources. Jenkins provides lock from the Lockable Resources plugin. But don't over-lock — you'll serialize your parallel pipeline back to sequential. Only lock the critical section. For database tests, use unique schema per branch (e.g., schema name with build number).
Matrix: Parallelism on Steroids (But Know the Cost)
Jenkins Declarative Matrix lets you run combinations of axes (e.g., OS × JDK version) in parallel. It's syntactic sugar over nested parallel stages. The cost: if you have 3 OSes × 4 JDKs = 12 combinations, you need 12 executors. On a small Jenkins, that's a denial-of-service attack on yourself. Always set executor limits via agent labels or use excludes to trim unnecessary combos. I've seen a matrix pipeline with 30 combinations lock up a 10-agent cluster for an hour. The fix: use failFast true and limit axes to what you actually need — do you really need JDK 8 and 11 on Windows?
excludes aggressively. If you only need JDK 17 on Linux, exclude Windows+JDK 17. Saves executor slots and reduces noise. Also, set failFast true on the matrix to abort all runs on first failure.When NOT to Use Parallel Execution
Parallelism isn't free. It adds complexity: you need to manage shared resources, handle failures gracefully, and ensure you have enough executors. Don't parallelize if: (1) your total build time is under 5 minutes — the overhead of spinning up agents and coordinating parallel branches may exceed the gain. (2) Your stages have tight dependencies — if stage B needs output from stage A, you can't parallelize them. (3) You're on a single-agent setup with 2 executors — parallelizing 10 branches will just queue them. (4) Your tests are flaky — parallel execution amplifies flakiness due to resource contention. Fix flaky tests first, then parallelize.
Debugging Parallel Pipelines: The Script Console Is Your Friend
When parallel pipelines go wrong, the UI shows 'Pending — waiting for executor'. But which executor? Why? Go to Manage Jenkins > Script Console and run Jenkins.instance.executors.each { println it.displayName + ' ' + it.isIdle() }. This shows all executors and their status. For stuck pipelines, run Jenkins.instance.getItemByFullName('jobName').getBuildByNumber(123).getExecutors() to see what's holding. Another common issue: parallel branches that use node block without specifying a label may all land on the same agent, causing resource contention. Use label to distribute.
org.jenkins.plugins.lockableresources.LockableResourcesManager.get().getResources().each { println it.name + ' locked by ' + it.lockedBy }.The 3 AM Executor Exhaustion
node block. Each node consumed an executor on the controller itself (not agents). The controller's executor pool (default 2) was exhausted, blocking even the main thread.executors on the master to 0 (use agents only). Or wrap parallel branches in agent none and allocate node explicitly on agents. Added failFast true and limited parallelism to 5.- Never run heavy parallel work on the Jenkins controller.
- Always delegate to agents and cap parallelism to your executor pool size.
Jenkins.instance.executors.each { println it.displayName + ' idle=' + it.isIdle() }. 3. Check for lock contention: org.jenkins.plugins.lockableresources.LockableResourcesManager.get().getResources().each { println it.name + ' locked by ' + it.lockedBy }. 4. Restart any hung agent.executorCount - 1. 3. Move heavy work to agents with more executors.excludes to remove unnecessary axis combinations. 2. Set failFast true on matrix. 3. Use agent labels to limit to specific agents. 4. Reduce axis values.Key takeaways
agent none at the pipeline level when using parallel with per-stage agents to avoid wasting an executor.failFast true to parallel blocks to stop all branches on first failureInterview Questions on This Topic
Frequently Asked Questions
20+ years shipping production infrastructure and CI/CD at scale. Notes here come from systems that actually shipped.
That's Jenkins. Mark it forged?
3 min read · try the examples if you haven't