Jenkins Configuration as Code, YAML Bundles, Secrets, Reloading, Validation, and Git-Managed Controller State: Guided Hands-On Workflow and Core Operations
Build a disposable Jenkins 2.568.3 LTS controller whose plugins are preinstalled separately from JCasC, whose YAML is mounted read-only from a tiny Git repository, and whose fake administrator password is resolved from a separate file-backed secret source. Then inspect, reload, create deliberate UI drift and reconcile it without exposing the secret.
Learning objectives
- Build a disposable Jenkins controller with JCasC and no production dependencies.
- Keep plugin installation, YAML and fake secret material in separate reproducible inputs.
- Verify startup state before making a configuration change.
- Reload JCasC deliberately and reconcile a controlled UI drift.
- Capture evidence without printing or archiving the fake secret.
1. Lab scenario and guardrails
Create a local controller named ch26-jenkins. The
built-in node remains at zero executors; this chapter is about
controller configuration, not build capacity. The image preinstalls
exact plugin versions, the JCasC directory is mounted read-only, and
the secret directory is mounted separately and excluded from Git.
ch26-* resources below. Do not point
CASC_JENKINS_CONFIG at a production configuration
repository, reuse a real admin password, or test reloads on an
uncontrolled controller.
2. Preflight: tools and expected identities
docker version
git --version
mkdir -p ch26/jcasc ch26/secrets ch26/evidence
cd ch26
git init
printf 'secrets/\nevidence/\n' > .gitignore
Record Docker and Git versions in
evidence/assumptions.txt. The verified course baseline
is Jenkins 2.568.3 LTS / Java 21, JCasC 2121.v86fe99d4b_b_a_b_ and
Credentials 1511.v2e3cb_0008ef0.
3. Build the controller image: plugins first
Create Dockerfile:
FROM jenkins/jenkins:2.568.3-lts-jdk21
USER jenkins
RUN jenkins-plugin-cli --plugins \
configuration-as-code:2121.v86fe99d4b_b_a_b_ \
credentials:1511.v2e3cb_0008ef0
JCasC does not install plugins. This image therefore establishes the configurator baseline before YAML is applied.
docker build -t ch26-jenkins:baseline .
docker image inspect ch26-jenkins:baseline --format '{{.Id}}' \
| tee evidence/controller-image-id.txt
4. Author the minimal JCasC source
Create jcasc/jenkins.yaml. The password is a
placeholder; the value will come from the mounted secret directory.
jenkins:
systemMessage: "Chapter 26 — config ${CH26_CONFIG_REV}"
numExecutors: 0
mode: EXCLUSIVE
securityRealm:
local:
allowsSignup: false
users:
- id: "lab-admin"
name: "Chapter 26 Lab Admin"
password: "${lab_admin_password}"
authorizationStrategy:
loggedInUsersCanDoAnything:
allowAnonymousRead: false
CH26_CONFIG_REV is non-secret provenance.
lab_admin_password is secret material and must never be
committed.
5. Create the fake secret outside Git
printf '%s' 'CH26_FAKE_ONLY_7e9c' > secrets/lab_admin_password
chmod 600 secrets/lab_admin_password
git add .gitignore Dockerfile jcasc/jenkins.yaml
git commit -m 'chapter26: baseline jcasc controller config'
BASE_COMMIT=$(git rev-parse HEAD)
printf '%s\n' "$BASE_COMMIT" | tee evidence/git-baseline.txt
sha256sum jcasc/jenkins.yaml | tee evidence/yaml-sha256-baseline.txt
The secret string above is synthetic and only for a loopback lab.
The secrets/ directory is ignored. Do not copy the
value into screenshots, console evidence or exported YAML.
6. Start the controller from three separate inputs
docker volume create ch26-home
docker run -d --name ch26-jenkins \
-p 127.0.0.1:8080:8080 \
-e JAVA_OPTS='-Djenkins.install.runSetupWizard=false' \
-e CASC_JENKINS_CONFIG=/var/jenkins_home/casc/jenkins.yaml \
-e CASC_STRICT_SECRET_RESOLUTION=true \
-e SECRETS=/run/ch26-secrets \
-e CH26_CONFIG_REV="$BASE_COMMIT" \
-v ch26-home:/var/jenkins_home \
-v "$PWD/jcasc:/var/jenkins_home/casc:ro" \
-v "$PWD/secrets:/run/ch26-secrets:ro" \
ch26-jenkins:baseline
docker logs -f ch26-jenkins
Wait until Jenkins reports readiness. Stop following logs with Ctrl+C; do not restart the container just because the log command is still attached.
7. Before-state inspection
Open http://127.0.0.1:8080 and sign in with the fake
lab account. Inspect:
- Manage Jenkins → Plugins → Installed: exact JCasC/Credentials versions.
- Manage Jenkins → Configuration as Code: source and current reference/documentation.
- Manage Jenkins → System: the system message and zero built-in executors.
- System Information: Jenkins/Java versions.
Save only non-secret screenshots/text. A successful login proves secret resolution; it is not permission to expose the password.
8. View/export configuration as evidence, not as source replacement
Use the Configuration as Code page to view or download the current
export into evidence/. Review it before retention. If
it contains any sensitive value in clear text, discard/sanitize the
copy and investigate rather than committing it.
Compare the export with authored YAML conceptually: the export can
include defaults and generated/plugin-specific representation. Keep
jcasc/jenkins.yaml as the reviewed source of truth.
9. Make a reviewed JCasC change and reload
Change only the system message:
jenkins:
systemMessage: "Chapter 26 — reviewed reload v2"
Preserve all other keys in the real file. Commit the edit:
git add jcasc/jenkins.yaml
git commit -m 'chapter26: update system message'
REV2=$(git rev-parse HEAD)
printf '%s\n' "$REV2" | tee evidence/git-reload.txt
sha256sum jcasc/jenkins.yaml | tee evidence/yaml-sha256-reload.txt
Because the directory is bind-mounted read-only, the running controller sees the updated host file. In Manage Jenkins → Configuration as Code, use Reload existing configuration. Reload is an administrative controller change; use this UI path for the lab rather than putting an API token or reload token in shell history.
10. Verify the reload independently
Confirm the new system message appears, the built-in executor count is still zero, the lab user can still authenticate, and the plugin inventory is unchanged. Preserve the reload timestamp and relevant controller log lines.
If the reload fails, do not keep clicking Reload. Preserve the first exception, compare the exact Git diff, and diagnose before another attempt.
11. Create controlled UI drift, then reconcile it
In Manage Jenkins → System, change the system
message to CH26 UI DRIFT — DO NOT KEEP and save. Verify
the UI now differs from Git. Record a screenshot/text note as drift
evidence.
Return to Configuration as Code and reload the Git-managed source.
The system message should return to
Chapter 26 — reviewed reload v2. That sequence proves
both drift and reconciliation.
12. Strict secret-resolution exercise
On a clone or after stopping this disposable controller,
temporarily rename the secret file and start/reload with
CASC_STRICT_SECRET_RESOLUTION=true. Expected result:
configuration application fails rather than silently replacing the
password with an empty value. Preserve the error, restore the file,
then recover the controller.
13. Challenge: choose the correct layer
A new YAML block configures a plugin, but the controller says no configurator exists for that element. Should you retry the build, add an agent, edit the workspace, or inspect plugin installation/schema?
Reason it out
Inspect the controller/plugin layer. Confirm the plugin short name/version is installed on the candidate controller and consult that instance’s JCasC reference. The queue and workspace cannot make a missing configurator appear.
14. Cleanup
docker rm -f ch26-jenkins 2>/dev/null || true
docker volume rm ch26-home 2>/dev/null || true
# Remove the local ch26 directory only after the evidence exercise is complete.
Never wildcard-delete Jenkins volumes. Confirm the exact
ch26-* resource names before removal.
Knowledge check
Answer before revealing the explanation.
1. Why is the JCasC plugin installed in the Docker image instead of declared inside jenkins.yaml?
JCasC cannot configure a plugin that is not installed, and the JCasC project explicitly does not support installing plugins through JCasC. Plugin supply and controller configuration are separate layers.
2. Why mount the YAML and secret directory read-only?
The lab treats Git-managed YAML and secret material as inputs, not mutable controller outputs. Read-only mounts make accidental writes visible and keep JENKINS_HOME from becoming the source of truth for those files.
3. How does the file secret stay out of Git?
Only the placeholder such as ${lab_admin_password} is committed. The value lives in a separate mounted directory referenced through the SECRETS path, and that directory is excluded from Git and evidence artifacts.
4. What does a successful reload prove—and what does it not prove?
It proves the current sources were accepted and applied by the installed configurators. It does not prove every job still works, that a restart would succeed, that the export is portable, or that external integrations are healthy.
5. Why deliberately make a UI drift change?
It demonstrates ownership: a setting managed by JCasC can temporarily diverge after a UI edit, but the next controlled reload/startup reasserts the versioned source of truth. The drift and reconciliation are both observable evidence.
Official references and version notes
- Jenkins — Configuration as Code — handbook overview and operating model.
-
Configuration as Code plugin
— current release, installation,
CASC_JENKINS_CONFIG, supplementary-source rules and plugin prerequisites. - JCasC upstream repository — canonical feature documentation, examples and compatibility notes.
- JCasC — Handling Secrets — SecretSource behavior, file/Docker/Kubernetes/Vault options and strict secret resolution.
- JCasC — Triggering Configuration Reload — UI, authenticated API and CLI reload paths and their privilege requirements.
- JCasC — JSON Schema — instance-specific schema support; current implementation remains beta and should not be the only validation gate.
- Credentials plugin — current credentials API baseline used by the disposable controller image.
- Jenkins LTS changelog and Java support policy — current core/runtime assumptions.
2.568.3 LTS with Java 21; Jenkins
2.568.3 is tested with Java 21 and 25. Configuration as Code is
2121.v86fe99d4b_b_a_b_, released 30 August 2026 and
requiring Jenkins 2.541.1. Credentials is
1511.v2e3cb_0008ef0, also requiring Jenkins 2.541.1.
The disposable image starts from
jenkins/jenkins:2.568.3-lts-jdk21; record its
platform-specific image digest before execution. Re-check versions,
security advisories and configurator reference output before reusing
the lab because plugin schemas can change independently of 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.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0Send only Ethereum/ERC-20 compatible assets to this
address.