pipeline, agent, stages, and steps

Scenario: A misplaced directive prevents parsing before any useful stage starts.

Reading your first real Jenkinsfile

Every Declarative Pipeline you'll ever see follows the exact same skeleton, and once you can read that skeleton, unfamiliar Jenkinsfiles stop looking scary. The three words to learn first are pipeline, stage, and step - and they nest inside each other in a very specific, very learnable order.

Analogy: Think of a Jenkinsfile like a recipe card with numbered sections. The whole card is the pipeline block - the entire recipe. Each numbered section, like "1. Prep vegetables" or "2. Cook sauce," is a stage - a named, visible phase of the work that shows up as its own box in Jenkins' stage-view UI. Inside each numbered section, the actual instructions - "dice the onion," "simmer for 10 minutes" - are the steps. You'd never write "dice the onion" as its own numbered section on the card; it belongs inside a stage, just like a Jenkins sh command belongs inside a steps block, not floating at the top level of the pipeline.

Declarative Pipeline enforces this shape strictly - it will refuse to even start if a step is in the wrong place, which feels strict at first but is actually a safety feature: it catches structural mistakes before any machine is even allocated to do work.

Pipeline example

pipeline { agent none; stages { stage('Test') { agent any; steps { sh './test.sh' } } } }

Declarative syntax is a model, not just a coding style

The required pipeline block contains an agent policy (which machine runs this) and stages (the named phases of work); each stage normally contains steps, or a supported nested parallel or matrix structure covered later in this track. Directives like when, environment, and options are not arbitrary Groovy statements you can place anywhere - their placement is part of the grammar itself. A stage can own its own agent, when, options, environment, input, tools, and post behavior. Because the structure is fixed and predictable, Jenkins can validate a Pipeline - catching a misplaced directive - before an executor is allocated or any external side effect (like a deployment) even begins.

agent any vs. agent none - and why it matters for files

agent any allocates a single workspace for the entire Pipeline up front, and every stage shares it. agent none avoids holding an executor while the top-level orchestration logic runs, and lets each individual stage pick its own appropriate node - but this means files do not automatically follow between stages' workspaces, since each stage might land on a completely different machine. Use stash and unstash to transfer small intra-run outputs between stages on different agents; use a real artifact repository for anything that needs to survive past this one build. A container-based agent (like agent { docker { image 'node:20' } }) changes which tools are available for the commands you run, while the controller is still the thing interpreting the overall Pipeline structure and scheduling the underlying node.

What a validation failure looks like - before any work even starts

WorkflowScript: 4: Expected one of "steps", "stages", "parallel", or "matrix" @ line 4, column 5.
       agent any
       ^

Finished: FAILURE

This is the Declarative linter catching a structural mistake - in this case, an agent directive placed somewhere it isn't allowed (like directly inside a stage block that already inherited one). Note what's genuinely helpful here: this failed before checking out any code or running a single command, because Jenkins parses and validates the whole structure first. That's very different from a runtime error deep inside a shell script that only shows up after ten minutes of real work.

Keep the visible graph honest

Use stage names for delivery boundaries a person would recognize (Build, Test, Deploy), and put the actual executable logic inside steps. A small script { ... } block is reasonable for one bit of logic Declarative genuinely can't express cleanly, but large imperative regions inside script blocks hide agent usage, conditions, and stage structure from both the linter and Jenkins' visual pipeline graph - defeating much of the point of using Declarative at all. Stable, known-in-advance dimensions (like "test on Node 18 and Node 20") belong in matrix or explicit named parallel stages rather than being generated dynamically in Groovy whenever it's avoidable.

A pitfall: only testing the success path

Run the Declarative linter, but also deliberately exercise failure paths, not just the happy case where everything parses and every step succeeds. Force a compilation failure, a test failure, a timeout, a skipped when condition, and an aborted input approval - then check that post conditions, reports, cleanup, and the final result all behave the way you expect. A well-designed Jenkinsfile lets a reviewer see where executors get allocated, where privileged actions happen, where approval gates sit, and what evidence gets produced - all without needing to mentally "execute" a general-purpose program in their head.

Goal: Practice the concrete behavior in jenkins-declarative-rescue. Learn sections have no Jenkins terminal; the Jenkins lab opens the real shared service in a new full-screen tab inside your private folder.