Chapter 11Lesson 01~115 minutes

Scripted Pipeline, Groovy Control Flow, node Blocks, Dynamic Logic, and When to Use Scripted Pipelines: Concepts, Architecture, and Mental Model

Understand Scripted Pipeline as a CPS-transformed Groovy orchestration program, distinguish controller-side control flow from agent-side work, and identify when dynamic logic justifies the additional flexibility.

Scripted PipelineGroovy CPSnodeControl flowSandboxDurability

Learning objectives

  • Explain how Scripted Pipeline differs from Declarative while using the same Pipeline execution engine.
  • Distinguish Groovy control-flow evaluation from Pipeline steps and agent-side shell commands.
  • Explain what a node block schedules, which workspace it creates, and why node scope is a resource decision.
  • Describe CPS persistence, suspension points, serialization constraints, and the safe purpose of @NonCPS.
  • Recognize when dynamic orchestration benefits from Scripted Pipeline and when Declarative remains clearer.

1. Why Scripted Pipeline exists

Chapter 10 used Declarative Pipeline to make common delivery structure explicit and validated. That structure is valuable, but some orchestration is genuinely dynamic: a validated list of components may be discovered at run time, different trusted inputs may require different node allocations, or a bounded loop may need to create a variable number of stages. Scripted Pipeline exposes more of Groovy's control-flow vocabulary for those cases.

The extra flexibility has a cost. A Scripted Jenkinsfile is not an ordinary Groovy program and it is not a shell script. Jenkins transforms most Pipeline Groovy into continuation-passing style (CPS) so it can save execution state around asynchronous Pipeline steps. That means you must reason about where code executes, what state must be serializable, when a node/workspace exists, and which APIs the Groovy sandbox permits.

Rule of thumb: prefer Declarative when the delivery graph is mostly known in advance. Reach for Scripted when bounded dynamic orchestration materially improves the design—not merely because Groovy lets you write clever code.

2. Mental model: CPS program → decisions → node work → durable state

A Scripted Jenkinsfile is compiled into Pipeline program state on the controller. Groovy expressions make orchestration decisions. Pipeline steps can suspend and resume. A node step enters the queue, waits for an eligible executor, and creates a workspace on the selected agent. Inside that context, sh/bat start external processes on the agent. Jenkins persists enough Pipeline state to resume supported work after expected interruptions.

Scripted Pipeline execution boundary
flowchart TD
 A[Jenkinsfile at exact SCM SHA] --> B[Groovy CPS program on controller]
 B --> C{Groovy control-flow decision}
 C --> D[Pipeline step]
 D --> E{node needed?}
 E -->|yes| F[Queue + label eligibility]
 F --> G[Executor + agent workspace]
 G --> H[sh/bat/tool process on agent]
 E -->|no| I[Controller-orchestrated Pipeline step]
 H --> J[Step result / files]
 I --> J
 J --> K[Persisted Pipeline run state]
 K --> L[Stage/build result + artifacts/evidence]

Do not interpret the diagram as “all Groovy is expensive controller work.” Most orchestration code is brief. The risk appears when large computations, huge in-memory objects, or controller APIs are used as if Jenkins were a general application server.

3. The smallest useful Scripted Pipeline

node('lab-linux') {
  stage('Observe') {
    echo "job=${env.JOB_NAME} build=${env.BUILD_NUMBER}"
    sh 'printf "node=%s\\nworkspace=%s\\n" "$NODE_NAME" "$WORKSPACE"'
  }
}

node('lab-linux') is a Pipeline step. It queues this body for an agent matching the label and gives the body a workspace. stage provides a named visualization/evidence boundary. echo is a Pipeline step. sh asks the agent to start a shell process. The shell receives environment variables such as NODE_NAME and WORKSPACE; Groovy does not become the shell.

4. Ordinary Groovy versus Pipeline steps

Construct Layer Can suspend? Operational meaning
if, for, list/map operations CPS-transformed Groovy program Not by themselves Decide orchestration flow; keep bounded.
node, sleep, input, checkout Pipeline steps Often yes Interact with Jenkins state and may persist/resume.
sh/bat Pipeline step + agent process Durable step Runs project/tool logic on the selected agent.
Java/Jenkins internal API call Controller JVM Not a normal Pipeline step Potentially powerful/unsafe; sandbox may reject it.
@NonCPS helper Normal Groovy execution for that method No Pipeline suspension inside Use only for small pure transformations that do not call Pipeline steps.

5. Bounded dynamic control flow

Scripted Pipeline can use familiar if/else, loops, maps, closures, and functions. The important word is bounded. The set of work should come from trusted or validated inputs, stage names should remain intelligible, and the resulting graph should not explode into hundreds of controller-managed branches accidentally.

def requested = ['lint', 'unit']
def allowed = ['lint', 'unit', 'package'] as Set

def selected = requested.findAll { allowed.contains(it) }

node('lab-linux') {
  selected.each { taskName ->
    stage("Check: ${taskName}") {
      sh "./lab-task '${taskName}'"
    }
  }
}

For an actual untrusted parameter, do not interpolate it into a shell command like this. Validate against an allowlist and pass it through a safer boundary. The example uses fixed synthetic values to focus on stage generation.

6. A node block is a scheduling and workspace decision

One large node block is easy to read but can hold an executor while controller-only decisions or waits occur. Several targeted node blocks can free capacity between phases and select different labels, but each allocation may use a different workspace or agent. If later phases require files, transfer them intentionally with artifacts/stashes or regenerate only when that behavior is explicitly safe.

Do not use the built-in controller node for routine builds. The controller runs Pipeline orchestration and trusted Jenkins internals. Repository-controlled build commands belong on appropriately isolated agents.

7. CPS, suspension, and serializable state

Most Pipeline Groovy is CPS-transformed. When a Pipeline reaches an asynchronous step, Jenkins can persist the program state. Variables that remain reachable across such a suspension may need to be serializable. Keeping a non-serializable object such as a regex Matcher, an open stream, or many third-party objects across a sleep, input, node, or other suspension point can produce NotSerializableException or confusing CPS behavior.

// Good pattern: reduce temporary object to a serializable String before suspension.
def releaseText = 'release-42'
def releaseId = (releaseText =~ /release-(\d+)/)[0][1].toString()
sleep 1
echo "releaseId=${releaseId}"

@NonCPS is not a blanket performance or compatibility fix. A method marked @NonCPS must not call Pipeline steps such as echo, sh, node, or sleep. Prefer simple serializable data structures and external tools on agents for substantial computation.

8. Sandbox and script approval are trust controls

Pipeline scripts normally run in the Groovy Sandbox. When code attempts a method or API that is not approved, Script Security can reject the signature. That rejection is evidence of a trust boundary, not an instruction to approve everything. An unsandboxed/approved script may call powerful Jenkins controller APIs and therefore belongs to a much stronger administrative trust domain.

Security boundary: do not disable the sandbox or bulk-approve signatures merely to make a lab pass. Rewrite the Pipeline to use supported Pipeline steps or a reviewed plugin/Shared Library API whenever practical.

9. When Scripted is justified

Situation Prefer Reason
Static build/test/package/deploy stages Declarative Validation and predictable structure are valuable.
Small conditional stage Declarative when No need for a general-purpose control-flow model.
Dynamic stage list from a validated local manifest Scripted or a tightly bounded Declarative script escape Dynamic orchestration is the actual requirement.
Large data transformation Agent-side program Do not turn controller-side Pipeline Groovy into an application workload.
Direct Jenkins internal API administration Dedicated admin mechanism Pipeline is not a safe substitute for JCasC, supported APIs, or maintained plugins.

10. Read-only inspection before editing

  • Record Jenkins core/Java and Pipeline: Groovy, Script Security, and Nodes/Processes plugin versions.
  • Record job full name, build number/URL, cause, and exact Jenkinsfile/source SHA.
  • Inspect existing node labels and agent/workspace evidence.
  • Identify every Groovy variable that may remain live across Pipeline steps.
  • List any sandbox rejection or approved signature without approving anything new.
  • Record stage names and build result so later dynamic changes can be compared.
Next lesson

Guided Hands-On Workflow and Core Operations

Write a bounded Scripted Pipeline, compare it with a Declarative equivalent, inspect controller-versus-agent execution, and keep node allocation intentional.

Knowledge check

What does node('lab-linux') do?

Why is Scripted Pipeline not ordinary Groovy?

Where should substantial build computation run?

What should you do when the Groovy sandbox rejects an internal API call?

When is Scripted Pipeline most defensible?

Official references and version notes

Version and compatibility note

Rechecked on 2026-09-16. Examples assume Jenkins 2.568.3 LTS, tested with Java 21 and 25, Pipeline: Groovy 4380.v6eb_8378b_9647, Script Security 1415.v9a_f9b_3a_c253d, and Pipeline: Nodes and Processes 1479.v56e587f413a_7. Pipeline: Groovy 4380 requires Jenkins 2.528.3 or newer. The mandatory lab uses a disposable lab-linux agent, a synthetic repository, no production credential, and no unsandboxed approval. Record your actually installed versions because plugin releases change independently from Jenkins core.

Keep the academy open

Support free, practical DevOps education.

Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0Send only Ethereum/ERC-20 compatible assets to this address.