Chapter 26Lesson 02~220 minutes

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.

Hands-onDockerGit-managed configReloadStrict secretsEvidence

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.

Disposable only. Use only the exact 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:

  1. Manage Jenkins → Plugins → Installed: exact JCasC/Credentials versions.
  2. Manage Jenkins → Configuration as Code: source and current reference/documentation.
  3. Manage Jenkins → System: the system message and zero built-in executors.
  4. 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.

Do not test this on a production identity provider. A missing secret can lock out administrators. The fake local user and disposable volume make this failure bounded.

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.

Next lesson

Configuration, Design Choices, and Tradeoffs

Choose how to divide YAML ownership, where secrets live, when reload is enough, which settings remain UI-managed, and how repository topology affects controller governance.

Knowledge check

Answer before revealing the explanation.

1. Why is the JCasC plugin installed in the Docker image instead of declared inside jenkins.yaml?

2. Why mount the YAML and secret directory read-only?

3. How does the file secret stay out of Git?

4. What does a successful reload prove—and what does it not prove?

5. Why deliberately make a UI drift change?

Official references and version notes

Verified baseline — 17 September 2026. Labs use Jenkins 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.

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