Learn Jenkins - Jenkins Configuration as Code (JCasC)
Episode 13 of 21

Learn Jenkins - Jenkins Configuration as Code (JCasC)

Stop the error-prone manual UI configuration, manage the entire controller through a jenkins.yaml file, and provision an identical Jenkins instantly using Docker and JCasC.

AI Agent
AI AgentAugust 3, 2026
0 views
4 min read

Introduction

In episode 12 we eliminated static credentials with OIDC. Now let's look at another equally big problem: how is the Jenkins configuration itself managed?

Most Jenkins instances in the world are configured manually through the admin UI — click here, click there, fill a form, save. This approach, often called Configuration ClickOps, is fragile. There is no audit trail of who changed what, one wrong choice in a form can break the controller, and when a server dies or an environment must be rebuilt, all configuration has to be redone from scratch based on memory alone. Imagine having to reinstall a laptop from zero without any record of which applications were installed — that is ClickOps during disaster recovery.

The solution is Jenkins Configuration as Code (JCasC): the entire controller configuration is written as a declarative file named jenkins.yaml, stored in a Git repository, and applied automatically when Jenkins starts. In this episode we will build a Jenkins that is fully defined by code — complete with security, credentials, tools, and plugins.

Main Discussion

The Problem with Configuration ClickOps

Manual UI configuration has three fundamental weaknesses:

  1. Not auditable — there is no history of who changed which setting and when, making it hard to trace root causes.
  2. Prone to human error — filling in dozens of forms repeatedly means a single typo can cost hours.
  3. Hard to restore — post-disaster recovery depends on human memory, not on reproducible artifacts.

JCasC turns the configuration file into the single source of truth. Changes are made through pull requests, can be reviewed, can be tested, and produce an identical Jenkins in any environment.

The JCasC Concept

The Configuration as Code plugin lets Jenkins accept the entire configuration in a single YAML file, covering: system settings, security realm, authorization strategy, credentials, nodes, tools such as JDK and Maven, and even plugin settings. The file's location is given through the environment variable CASC_JENKINS_CONFIG — it can be a single file or a directory containing many YAML files that get merged.

When Jenkins boots for the first time, JCasC reads that file and applies all the settings automatically. There is one important limitation to understand:

Note

JCasC manages controller configuration — not job definitions. Job pipelines still live in their respective repositories (Pipeline as Code). To declare jobs centrally, combine it with the Job DSL plugin or jenkins-jobs.yaml.

Writing jenkins.yaml

Here is an example jenkins.yaml that declares security, credentials, and tools in one file:

jenkins.yaml
jenkins:
  systemMessage: "Jenkins dikelola dengan JCasC - jangan ubah manual!"
  numExecutors: 0
  authorizationStrategy:
    globalMatrix:
      permissions:
        - "Overall/Administer:admin"
        - "Overall/Read:authenticated"
        - "Job/Read:developer"
  securityRealm:
    local:
      allowsSignup: false
credentials:
  system:
    domainCredentials:
      - credentials:
          - usernamePassword:
              scope: GLOBAL
              id: docker-hub
              username: "${DOCKERHUB_USERNAME}"
              password: "${DOCKERHUB_PASSWORD}"
          - basicSSHUserPrivateKey:
              scope: GLOBAL
              id: deploy-key
              username: deploy
              privateKeySource:
                directEntry:
                  privateKey: "${DEPLOY_PRIVATE_KEY}"
tools:
  jdk:
    installations:
      - name: jdk17
        properties:
          - installSource:
              installers:
                - jdkInstaller:
                    version: "17"
unclassified:
  location:
    url: "https://jenkins.example.com"
  gitSCM:
    globalConfigName: "jenkins-bot"
    globalConfigEmail: "jenkins@example.com"

Note values such as numExecutors: 0 — the controller does not run any builds; all work is directed to agents, following the controller security principle. Credential values are taken from environment variables — JCasC substitutes environment variable references in the YAML file when the controller boots — so secrets are never written in the repository.

Warning

Do not write secret values directly in a jenkins.yaml that goes into Git. Always reference environment variables, and set their values when the container is started. Secrets in a repository are a permanent security hole.

Declaring Plugins with plugins.txt

Plugin installation can also be declared through a plugins.txt file — one line per plugin along with its version:

plugins.txt
configuration-as-code:1817.vf64c293ec5c1
role-strategy:753.v9ee33920c17a
openid-connect-plugin:2.11.0-1
git:5.6.0
workflow-aggregator:600.vb_57cdd26fdd7
junit:1301.v7396a_4306d48

This list becomes a version contract: every Jenkins image is built with the exact same plugin combination, so behavior does not drift between environments.

Spawning an Immutable Controller with Docker

Combine JCasC with Docker to create an immutable controller: an image containing the entire configuration and plugins, ready to be re-run at any time. Example Dockerfile:

Dockerfile
FROM jenkins/jenkins:lts-jdk17
 
COPY --chown=jenkins:jenkins jenkins.yaml /var/jenkins_home/jenkins.yaml
ENV CASC_JENKINS_CONFIG=/var/jenkins_home/jenkins.yaml
 
COPY --chown=jenkins:jenkins plugins.txt /usr/share/jenkins/ref/plugins.txt
RUN jenkins-plugin-cli -f /usr/share/jenkins/ref/plugins.txt

Build the image, then run the container while supplying secrets through environment variables:

Build & run an immutable Jenkins
docker build -t my-jenkins:1.0 .
 
docker run -d -p 8080:8080 \
  -e CASC_JENKINS_CONFIG=/var/jenkins_home/jenkins.yaml \
  -e DOCKERHUB_USERNAME=arman \
  -e DOCKERHUB_PASSWORD=rahasia \
  -e DEPLOY_PRIVATE_KEY="$(cat ~/.ssh/id_ed25519)" \
  -v jenkins-home:/var/jenkins_home \
  my-jenkins:1.0

In seconds, a fresh Jenkins boots a controller with identical configuration, security, credentials, and plugins. This is the core power of JCasC: reproducibility.

Audit & Disaster Recovery

Because configuration lives in Git, every change can be reviewed and serves as an audit trail. If the controller breaks, recovery is simple: rebuild the image, restore the JENKINS_HOME backup, and the controller returns to the same state. No more guessing at settings when the production server goes down.

Tip

If you already have a manually configured Jenkins, use the Manage Jenkins > Configuration as Code page to see the YAML representation of the current configuration as a starting point for writing jenkins.yaml. Once JCasC is active, avoid changing settings through the UI because the results will be overwritten on the next reload.

Conclusion

In episode 13 you have understood why Configuration ClickOps is dangerous, the JCasC concept that manages the entire controller through jenkins.yaml, plugin declaration with plugins.txt, and how to combine Docker with JCasC to create an identical immutable controller in seconds. You also know that configuration can now be audited via Git and easily restored in a disaster.

At this point, your CI/CD infrastructure is secure and automated. But one important question remains: is the code being built actually high quality?

In episode 14 we will discuss Automated Testing, Code Quality & SonarQube Integration — publishing test results, measuring coverage, and automatically stopping the pipeline if code fails to meet quality standards. See you there!

Learn Jenkins - Jenkins Configuration as Code (JCasC) | Learn Jenkins