Declarative Pipeline Syntax, agent, stages, steps, options, parameters, environment, tools, and post: Guided Hands-On Workflow and Core Operations
Construct and validate a disposable Declarative Pipeline step by step, add parameters/environment/tools/options/stage agents/post, inspect evidence, then introduce and repair a structural error.
Learning objectives
- Create an SCM-backed Declarative Pipeline using a synthetic repository.
- Add directives incrementally and explain which state each one changes.
- Use top-level agent none with stage-level allocation to make executor ownership visible.
- Capture build, node, workspace, tool, parameter, and stage evidence without exposing secrets.
- Introduce a structural Declarative error, preserve its validation evidence, and repair only that layer.
1. Disposable topology and preflight
Reuse the authorized local controller and
lab-linux agent pattern from Chapters 08–09. The
repository is synthetic. The job name in this lesson is
ch10-declarative-lab. No cloud account, production SCM
organization, or real deployment target is required.
jdk21-build and maven-3.9 either exist or
are replaced consistently with your verified Chapter 08 names.
Record the exact tool binaries those names resolve to with
java -version and mvn -version during the
build. A Jenkins tool name is configuration identity, not proof of
binary version.
2. Create a synthetic source repository
mkdir jenkins-ch10-demo && cd jenkins-ch10-demo
git init
git config user.name "Jenkins Chapter 10 Lab"
git config user.email "jenkins-ch10@example.invalid"
printf 'chapter10\n' > payload.txt
git add payload.txt
git commit -m 'ch10: add payload'
git rev-parse HEAD
Save the SHA. The Jenkinsfile will be committed into the same repository so the Pipeline definition and payload have one immutable source identity.
3. Increment 1 — validate the smallest Pipeline
Start deliberately small. Use agent none so agent
allocation becomes visible in the next increment.
pipeline {
agent none
stages {
stage('Model') {
steps {
echo 'Declarative model accepted.'
}
}
}
}
This form is structurally valid, but the echo step does
not require a workspace. Commit it. Create a Pipeline-from-SCM job
and run it. Preserve the build number and exact Jenkinsfile SHA.
4. Increment 2 — add parameter and global environment policy
pipeline {
agent none
options {
timestamps()
disableConcurrentBuilds()
buildDiscarder(logRotator(numToKeepStr: '20'))
}
parameters {
choice(name: 'TARGET', choices: ['sandbox', 'qa'], description: 'Synthetic target')
booleanParam(name: 'RUN_TESTS', defaultValue: true, description: 'Execute test stage')
}
environment {
APP_NAME = 'ch10-demo'
}
stages {
stage('Model') {
steps {
echo "app=${env.APP_NAME} target=${params.TARGET} tests=${params.RUN_TESTS}"
}
}
}
}
disableConcurrentBuilds() changes job/run scheduling
behavior; it is not a shell mutex.
buildDiscarder changes retained run history policy. The
parameters become build inputs; the environment value becomes
available to enclosed steps.
5. Increment 3 — allocate a stage agent and tools
Add a stage that needs a workspace. Keep the tools next to the stage-level agent so the ownership boundary is obvious.
stage('Build evidence') {
agent { label 'lab-linux' }
tools {
jdk 'jdk21-build'
maven 'maven-3.9'
}
options {
timeout(time: 5, unit: 'MINUTES')
}
steps {
sh '''
set -eu
mkdir -p evidence
{
printf 'job=%s\n' "$JOB_NAME"
printf 'build=%s\n' "$BUILD_NUMBER"
printf 'node=%s\n' "$NODE_NAME"
printf 'workspace=%s\n' "$WORKSPACE"
printf 'source_sha=%s\n' "$(git rev-parse HEAD)"
printf 'target=%s\n' "$TARGET"
java -version 2>&1
mvn -version
} > evidence/build.txt
'''
archiveArtifacts artifacts: 'evidence/*', fingerprint: true
}
}
The stage-level timeout is applied before entering the stage agent; therefore queue/agent-allocation time can count against that stage timeout. This differs from a top-level Pipeline timeout with a top-level agent, where agent allocation occurs before the timeout starts.
6. Increment 4 — conditionally run a test stage
stage('Test') {
when {
expression { return params.RUN_TESTS }
}
agent { label 'lab-linux' }
steps {
sh '''
set -eu
test "$TARGET" = sandbox -o "$TARGET" = qa
printf 'tests-ok target=%s\n' "$TARGET" > test-result.txt
'''
}
post {
always {
archiveArtifacts artifacts: 'test-result.txt', allowEmptyArchive: true
}
}
}
when controls whether the stage executes. A skipped
stage is not a failed stage. The allowlist check still exists inside
the executable step because build inputs are untrusted and
conditions should not be the only safety boundary.
7. Increment 5 — wait without holding an executor
Add a purely synthetic approval stage. The stage-level
input directive waits before acquiring its declared
agent.
stage('Approve simulation') {
input {
message 'Run the bounded synthetic side effect?'
ok 'Continue'
}
agent { label 'lab-linux' }
steps {
sh '''
set -eu
case "$TARGET" in
sandbox|qa) ;;
*) echo 'rejected target' >&2; exit 2 ;;
esac
printf 'simulated target=%s build=%s\n' "$TARGET" "$BUILD_NUMBER" > side-effect.txt
'''
archiveArtifacts artifacts: 'side-effect.txt', fingerprint: true
}
}
The “side effect” is only a local text file. The lab teaches control flow without touching a production service.
8. Add Pipeline-level post
Because the Pipeline uses agent none, do not assume a
global workspace in top-level post. Keep
workspace-bound artifact archiving in stages that own an agent; use
the top-level block for controller-level evidence such as final
status text.
post {
always {
echo "finalResult=${currentBuild.currentResult} build=${env.BUILD_NUMBER}"
}
failure {
echo 'Preserve the failing build URL and first error before rerun.'
}
cleanup {
echo 'No external resources were created by this mandatory lab.'
}
}
9. Validate before spending agent time
Use the Pipeline Syntax/Declarative validation capability available on your controller or simply run the SCM-backed lab after committing the Jenkinsfile. Record whether failure occurs before any agent allocation. A structural validation error should not be “fixed” by restarting an agent.
10. Deliberately break one directive
Create a new commit that moves parameters inside a
stage:
stage('Broken') {
parameters {
string(name: 'ILLEGAL_HERE', defaultValue: 'x')
}
steps { echo 'This model should be rejected.' }
}
Trigger/validate that revision and preserve the exact validation message, build URL (if a run record is created), and source SHA. The expected causal layer is the Declarative model, not Git, queue capacity, or shell execution.
11. Repair only the structural layer
Move the parameter back to the top-level
parameters directive, commit a new revision, and run
again. Do not simultaneously change plugins, agents, credentials, or
tools. A narrow repair keeps the comparison explainable.
12. Required evidence
- Jenkins/Java/Pipeline/Declarative version baseline.
- Job full name, build number/URL, cause, Jenkinsfile/source SHA.
-
Configured tool names plus observed
java -version/mvn -version. - Parameter values that are safe to record; never secret values.
- Node/label/workspace for agent-backed stages.
- Stage outcomes including skipped/failed/success states.
- Validation error from the intentionally broken revision and the repairing revision SHA.
- Archived evidence files and fingerprints.
13. Small challenge: choose the layer
The Jenkinsfile validates, but maven 'maven-3.9' cannot
resolve on the chosen agent. Should you modify Declarative grammar,
restart the controller, or inspect tool configuration/agent
compatibility?
The correct layer is tool configuration/execution context. Preserve the run evidence, inspect the configured tool name and agent OS/architecture, then correct the smallest mismatch.
14. Cleanup
Keep the evidence packet. Delete only the synthetic job/repository resources created for Chapter 10. Do not remove shared Pipeline plugins, global tools, credentials, or agents used by other labs.
Knowledge check
Why use agent none in this lab?
It makes stage-specific allocation visible and avoids reserving one executor for stages that do not need an agent.
What is the causal layer of a misplaced
parameters directive?
Declarative model validation, before agent/tool/shell runtime behavior.
Why record actual java -version and
mvn -version even when tool names are
pinned?
A Jenkins tool name is configuration identity; runtime output proves which binaries the agent actually used.
Why is stage-level input useful before an
expensive agent?
It can wait for approval before the stage acquires the agent/executor.
Why keep workspace-dependent archiving inside an agent-backed
stage when top-level agent none is used?
Because top-level post processing must not assume it owns a workspace.
Official references and version notes
- Jenkins LTS changelog — current LTS line and tested Java configurations.
-
Pipeline Syntax
— authoritative Declarative sections/directives, scopes, options,
parameters, environment, tools,
when,input, andpost. - Using a Jenkinsfile — Pipeline-as-Code, environment, credentials, parameters, and source-controlled Jenkinsfiles.
- Jenkins Pipeline handbook — Pipeline concepts and execution model.
- Pipeline: Declarative — Declarative Pipeline implementation and current compatibility.
- Pipeline — Pipeline plugin aggregator baseline.
- Credentials Binding — credential-binding behavior and secret-handling cautions.
- Pipeline: Stage View — optional visualization; it is not the source of execution truth.
Rechecked on 2026-09-16. Examples assume
Jenkins 2.568.3 LTS, tested with Java 21 and 25,
Pipeline aggregator 608.v67378e9d3db_1, Pipeline:
Declarative 2.2293.v6e7193cec599, Credentials
Binding 728.v902a_273b_8947, and optional Stage
View 2.41. Declarative 2.2293 requires Jenkins
2.504.3 or newer. Labs reuse the Chapter 08 tool names
jdk21-build and maven-3.9; verify what
exact binaries those names resolve to on your disposable agent
before running. Plugin versions and supported directives change
independently, so capture the installed baseline rather than
assuming these versions forever.
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.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0Send only Ethereum/ERC-20 compatible assets to this
address.