Jenkins and CI/CD Foundations, Controller-Agent Architecture, Jobs, Builds, and Automation Boundaries: Guided Hands-On Workflow and Core Operations
Build the Chapter 01 mental model with a disposable controller and a separate inbound agent. You will pin the Jenkins baseline, complete the setup wizard safely, remove routine build execution from the built-in node, run a two-stage Pipeline, preserve build evidence, and then connect the same Pipeline to SCM so a source change can become a distinct build cause.
Learning objectives
- Launch a disposable Jenkins 2.568.3 LTS controller with persistent lab-only state and verify its Java/core baseline before setup.
- Create a separate single-executor inbound agent over WebSocket and confirm that routine builds do not use the built-in node.
- Create and run a minimal Declarative Pipeline while inspecting queue, build, node, workspace, console, and artifact evidence.
- Convert the Pipeline to “Pipeline script from SCM,” record the exact checked-out SHA, and distinguish manual and SCM-triggered causes.
- Clean up only the controller, agent, Docker network/volume, and disposable repository created by this lab.
1. Preflight: define the lab boundary before creating resources
This lab is intentionally local-first. The controller and agent run as separate Docker containers on a dedicated Docker network. The controller keeps state in a named volume so a container restart does not silently erase the lab. The agent has one executor and a dedicated work directory. No production credentials, cloud accounts, internal repositories, or privileged Docker socket mounts are required.
| Assumption | Pinned/required value | Why it matters |
|---|---|---|
| Controller | jenkins/jenkins:2.568.3-lts-jdk21 |
Pins current LTS core and Java 21 runtime for the lab. |
| Controller image index |
sha256:c1e4c349365f6d16d88595b2c5f7e8ff39b8ae1d061f62420bac193b4b9616d0
|
Documents the multi-platform image identity observed at verification time. |
| Agent |
jenkins/inbound-agent:3391.va_37fa_a_305d6d-2-jdk21
|
Pins current inbound-agent/Remoting packaging for Java 21. |
| Pipeline |
workflow-aggregator 608.v67378e9d3db_1 plus
current dependencies
|
Provides core Pipeline steps; record actual resolved dependency versions. |
| Declarative |
pipeline-model-definition 2.2293.v6e7193cec599
|
Provides Declarative Pipeline syntax used by the lab. |
| Host | Docker Engine/Desktop, at least ~4 GB free memory and ~8 GB free disk | Prevents resource pressure from being confused with Jenkins behavior. |
2. Start the disposable controller and prove what version actually ran
Create names first so cleanup can target exact resources instead of
broad Docker commands. Port 18080 avoids assuming the
host has nothing on 8080.
set -eu
docker network create jenkins-ch01-net
docker volume create jenkins-ch01-home
docker run -d \
--name jenkins-ch01-controller \
--network jenkins-ch01-net \
--restart=no \
-p 127.0.0.1:18080:8080 \
-v jenkins-ch01-home:/var/jenkins_home \
jenkins/jenkins:2.568.3-lts-jdk21
# Preserve startup evidence before opening the UI.
docker logs --tail 120 jenkins-ch01-controller
docker exec jenkins-ch01-controller java -version
docker exec jenkins-ch01-controller java -jar /usr/share/jenkins/jenkins.war --version
Expected observation: Jenkins reports 2.568.3 and
Java 21. If either differs, stop and resolve the image/tag mismatch
before building lesson evidence. Open
http://localhost:18080/ only after the logs show that
Jenkins is ready.
3. Complete initial setup without weakening security
Read the one-time setup password directly from the disposable controller, complete the setup wizard, install suggested plugins, and create a lab-only administrator. Do not disable authentication, CSRF, Script Security, or TLS verification to “make the lab easier.” The local HTTP endpoint is acceptable only because it is bound to the learner's machine for this disposable exercise; production exposure is a later architecture topic.
docker exec jenkins-ch01-controller \
cat /var/jenkins_home/secrets/initialAdminPassword
After setup, open
Manage Jenkins → Plugins → Installed plugins.
Verify that Pipeline and Declarative Pipeline support are present.
Record the actual versions in a small text note. On the verification
date for this lesson, the current reference versions are
workflow-aggregator 608.v67378e9d3db_1 and
pipeline-model-definition 2.2293.v6e7193cec599;
dependencies can resolve to newer compatible versions in the future,
so the observation matters more than memorizing a number.
4. Remove build execution from the built-in node
Navigate to
Manage Jenkins → Nodes → Built-In Node → Configure.
Set Number of executors to 0 and save.
Do this only after you understand the next step: with no other
agent, builds will queue rather than run.
5. Create a single-executor inbound agent over WebSocket
Go to Manage Jenkins → Nodes → New Node. Create a
permanent agent named lab-agent with remote root
/home/jenkins/agent, one executor, labels
lab linux, usage “Only build jobs with label
expressions matching this node” if available, and launch method
Launch agent by connecting it to the controller.
Save it and open the agent page to obtain its secret.
In a Bash-compatible terminal, read the secret without echoing it, start the pinned agent image on the same Docker network, then remove the shell variable:
read -rsp 'Disposable Jenkins agent secret: ' JENKINS_AGENT_SECRET
echo
docker run -d --init \
--name jenkins-ch01-agent \
--network jenkins-ch01-net \
-e JENKINS_URL=http://jenkins-ch01-controller:8080/ \
-e JENKINS_SECRET="$JENKINS_AGENT_SECRET" \
-e JENKINS_AGENT_NAME=lab-agent \
-e JENKINS_AGENT_WORKDIR=/home/jenkins/agent \
-e JENKINS_WEB_SOCKET=true \
jenkins/inbound-agent:3391.va_37fa_a_305d6d-2-jdk21
unset JENKINS_AGENT_SECRET
docker logs --tail 80 jenkins-ch01-agent
Expected observation: the node becomes online, reports one executor,
and carries the lab/linux labels. The
WebSocket path means the lab does not need to publish Jenkins'
inbound TCP agent port. Record the agent image tag, node name,
labels, and Java/Remoting details shown by Jenkins. The disposable
secret is still present in the agent container's environment and can
be read by a local Docker administrator; remove the lab
agent/container when the exercise is complete and never treat
container environment variables as a secret vault.
6. Create the smallest Pipeline that produces inspectable evidence
Create a new Pipeline item named
jenkins-ch01-observe. For the first run, store the
Pipeline script in the job configuration so source-control
integration does not hide the scheduling model you are learning. Use
this script:
pipeline {
agent { label 'lab' }
stages {
stage('Observe') {
steps {
sh '''
set -eu
mkdir -p out
{
printf 'job=%s\n' "$JOB_NAME"
printf 'build=%s\n' "$BUILD_NUMBER"
printf 'build_url=%s\n' "$BUILD_URL"
printf 'node=%s\n' "$NODE_NAME"
printf 'workspace=%s\n' "$WORKSPACE"
java -version 2>&1 | head -n 1
} | tee out/runtime.txt
'''
}
}
stage('Retain evidence') {
steps {
sh 'sha256sum out/runtime.txt > out/runtime.txt.sha256'
archiveArtifacts artifacts: 'out/*', fingerprint: true
}
}
}
}
Click Build Now. Before opening the console, notice
whether the run briefly appears in the queue. Then inspect the build
page, cause, node/executor allocation, console log, workspace path,
and archived artifacts. The build should execute on
lab-agent, never the built-in node.
The first run should show a user-initiated cause.
Record full job name, build number, and build URL.
If visible, record queue/wait reason and allocation transition.
Record lab-agent, labels, executor, Java/Remoting
identity.
Record the path, but treat it as transient execution state.
Download runtime.txt and its SHA-256 file from the
build record.
7. Move the same Pipeline into SCM and create an SCM-triggered run
The second path adds source identity. Create a tiny disposable
public repository in a free Git hosting service (GitHub or GitLab is
sufficient) containing only a Jenkinsfile and a
README.md. Do not use a proprietary repository or
credentials. Put the following Pipeline in Jenkinsfile:
pipeline {
agent { label 'lab' }
triggers {
pollSCM('H/2 * * * *')
}
stages {
stage('Observe source') {
steps {
sh '''
set -eu
mkdir -p out
{
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)"
git status --short
} | tee out/identity.txt
'''
}
}
stage('Retain evidence') {
steps {
sh 'sha256sum out/identity.txt > out/identity.txt.sha256'
archiveArtifacts artifacts: 'out/*', fingerprint: true
}
}
}
}
In the job configuration, change Definition to
Pipeline script from SCM, choose Git, point at the
disposable public repository, use the default branch, and set Script
Path to Jenkinsfile. No repository credential is needed
for a public lab repository. Save, then run one manual build and
record its exact git rev-parse HEAD.
Now commit and push a harmless README change. Because the
Jenkinsfile declares pollSCM, Jenkins will poll on its
hashed schedule and create a run after it detects a revision change.
Inspect the new build cause and verify that the checked-out SHA
differs from the previous build. Polling is used here because a
localhost controller is normally not reachable by a hosted SCM
webhook; webhook design belongs in later lessons.
8. Verification checklist: prove each state independently
- Controller reports Jenkins 2.568.3 and Java 21 for this dated lab baseline.
- Built-in node has zero executors.
-
lab-agentis online, has one executor, and carries the expected labels. -
The manual build shows a user cause and ran on
lab-agent. - The SCM build records a different cause and the exact checked-out commit SHA.
- Both builds retain evidence artifacts outside the live workspace.
- No real credential, agent secret, private source, or production URL appears in console output or archived artifacts.
9. Small challenge: diagnose the layer before touching the script
Edit the Pipeline label from lab to
lab-missing and schedule one build. Predict the outcome
first. The correct prediction is not “the shell fails”: no eligible
node exists, so the Pipeline should wait before the agent-executed
steps begin. Capture the queue reason, then restore the label and
let the run proceed. This separates scheduling failure from script
failure.
10. Cleanup: remove only resources created by this lab
Before cleanup, export or screenshot the evidence you want to keep. Then delete the Jenkins item and node from the disposable controller if desired, stop/remove only the named containers, remove the dedicated network, and remove the lab volume only when you are sure you do not need the controller state.
docker rm -f jenkins-ch01-agent jenkins-ch01-controller
docker network rm jenkins-ch01-net
# Destructive: removes the disposable controller's Jenkins home.
# Run only after verifying the exact volume name.
docker volume inspect jenkins-ch01-home
docker volume rm jenkins-ch01-home
If you created an external disposable repository, delete it through that provider only after checking its exact owner/name and confirming it contains no unrelated work.
11. What the lab proved
You now have direct evidence that the controller, queue, agent, executor, workspace, source revision, build record, and artifact store are distinct states. You also observed two different causes for the same logical automation and proved that a source-triggered build must be tied to an immutable SHA rather than a moving branch label.
Knowledge check
Why did the lab set built-in-node executors to zero before routine builds?
To keep build-controlled execution off the controller process/filesystem and to make controller versus agent failures independently observable.
A build with label lab-missing waits in the queue.
Which layer is failing?
Scheduling/eligibility. The agent-executed shell has not started, so changing the shell command would not address the cause.
Why archive runtime.txt instead of relying on the
workspace copy?
The workspace is mutable and may disappear. The archived copy is attached to a specific Jenkins build record and can be retained independently.
Why is SCM polling acceptable in this localhost lab even though webhooks are often preferred in production?
It creates a genuine SCM-driven cause without exposing a local controller to the internet. Webhook design, authentication, and ingress are separate operational topics.
What should you record before deleting the controller volume?
The exact resource identity plus any needed build evidence, plugin/core/Java baseline, source SHAs, artifacts, and lab notes. The volume deletion is irreversible lab cleanup.
Official references and version notes
- Jenkins documentation — primary documentation hub.
- Jenkins Pipeline and Pipeline syntax — current Pipeline mental model and Declarative syntax.
- Java Support Policy — supported Java runtimes for Jenkins core, agents, and CLI components.
- Controller Isolation and Managing Nodes — controller/agent trust and executor guidance.
- Jenkins LTS changelog and Security advisories — current release/security state.
- Official Jenkins controller image and official inbound-agent image.
- Pipeline plugin and Pipeline: Declarative plugin — current plugin versions/minimum core requirements.
Rechecked against primary Jenkins sources on
2026-09-13. The executable Chapter 01 baseline is
2.568.3 LTS on
Java 21 (Java 25 is also supported by this LTS line), controller image
jenkins/jenkins:2.568.3-lts-jdk21, and inbound-agent
image
jenkins/inbound-agent:3391.va_37fa_a_305d6d-2-jdk21.
The current LTS line can change after this date, so future
generation and later lab reuse must re-check the LTS changelog,
Java support matrix, Docker tags, plugin minimum core versions,
and security advisories.
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.